Krita Source Code Documentation
Loading...
Searching...
No Matches
KisBezierTransformMesh.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
13#include "kis_debug.h"
14
16KisBezierTransformMesh::hitTestPatchImpl(const QPointF &pt, QPointF *localPointResult) const
17{
18 auto result = endPatches();
19
20 const QRectF unitRect(0, 0, 1, 1);
21
22 for (auto it = beginPatches(); it != endPatches(); ++it) {
23 Patch patch = *it;
24
25 if (patch.dstBoundingRect().contains(pt)) {
26 const QPointF localPos = KisBezierUtils::calculateLocalPos(patch.points, pt);
27
28 if (unitRect.contains(localPos)) {
29
30 if (localPointResult) {
31 *localPointResult = localPos;
32 }
33
34 result = it;
35 break;
36 }
37 }
38 }
39
40 return result;
41}
42
43KisBezierTransformMesh::PatchIndex KisBezierTransformMesh::hitTestPatch(const QPointF &pt, QPointF *localPointResult) const
44{
45 return hitTestPatchImpl(pt, localPointResult).patchIndex();
46}
47
49{
50 const QRectF searchRect = rect & m_originalRect;
51
52 if (searchRect.isEmpty()) return QRect();
53
54 const QPointF proportionalTL = KisAlgebra2D::absoluteToRelative(searchRect.topLeft(), m_originalRect);
55 const QPointF proportionalBR = KisAlgebra2D::absoluteToRelative(searchRect.bottomRight(), m_originalRect);
56
57 const auto topItY = prev(upper_bound(m_rows.begin(), prev(m_rows.end()), proportionalTL.y()));
58 const int topRow = distance(m_rows.begin(), topItY);
59
60 const auto leftItX = prev(upper_bound(m_columns.begin(), prev(m_columns.end()), proportionalTL.x()));
61 const int leftColumn = distance(m_columns.begin(), leftItX);
62
63 const auto bottomItY = prev(upper_bound(m_rows.begin(), prev(m_rows.end()), proportionalBR.y()));
64 const int bottomRow = distance(m_rows.begin(), bottomItY);
65
66 const auto rightItX = prev(upper_bound(m_columns.begin(), prev(m_columns.end()), proportionalBR.x()));
67 const int rightColumn = distance(m_columns.begin(), rightItX);
68
69 return QRect(leftColumn, topRow,
70 rightColumn - leftColumn + 1,
71 bottomRow - topRow + 1);
72}
73
74void KisBezierTransformMesh::transformPatch(const KisBezierPatch &patch, const QPoint &srcQImageOffset, const QImage &srcImage, const QPoint &dstQImageOffset, QImage *dstImage)
75{
76 QVector<QPointF> originalPointsLocal;
77 QVector<QPointF> transformedPointsLocal;
78 QSize gridSize;
79
80 patch.sampleRegularGrid(gridSize, originalPointsLocal, transformedPointsLocal, QPointF(8,8));
81
82 const QRect dstBoundsI = patch.dstBoundingRect().toAlignedRect();
83 const QRect imageSize = QRect(dstQImageOffset, dstImage->size());
84 KIS_SAFE_ASSERT_RECOVER_NOOP(imageSize.contains(dstBoundsI));
85
86 {
87 GridIterationTools::QImagePolygonOp polygonOp(srcImage, *dstImage, srcQImageOffset, dstQImageOffset);
88
92 gridSize,
93 originalPointsLocal,
94 transformedPointsLocal);
95 }
96}
97
99{
100 QVector<QPointF> originalPointsLocal;
101 QVector<QPointF> transformedPointsLocal;
102 QSize gridSize;
103
104 patch.sampleRegularGrid(gridSize, originalPointsLocal, transformedPointsLocal, QPointF(8,8));
105
106 {
107 GridIterationTools::PaintDevicePolygonOp polygonOp(srcDevice, dstDevice);
108
112 gridSize,
113 originalPointsLocal,
114 transformedPointsLocal);
115 }
116}
117
118void KisBezierTransformMesh::transformMesh(const QPoint &srcQImageOffset, const QImage &srcImage, const QPoint &dstQImageOffset, QImage *dstImage) const
119{
120 for (auto it = beginPatches(); it != endPatches(); ++it) {
121 transformPatch(*it, srcQImageOffset, srcImage, dstQImageOffset, dstImage);
122 }
123}
124
126{
127 for (auto it = beginPatches(); it != endPatches(); ++it) {
128 transformPatch(*it, srcDevice, dstDevice);
129 }
130}
131
132QRect KisBezierTransformMesh::approxNeedRect(const QRect &rc) const
133{
134 QRect result;
135
136 const QRect sampleRect = rc & dstBoundingRect().toAlignedRect();
137 if (sampleRect.isEmpty()) return result;
138
139 const QRectF unitRect(0, 0, 1, 1);
140 const int samplesLimit = sampleRect.width() * sampleRect.height() / 2;
141
142 QRectF stepRect;
143
144 {
155 const QRectF dstRect = rc;
156
157 auto tryAddHandle = [&dstRect, &stepRect] (const KisBezierPatch &patch, KisBezierPatch::ControlPointType controlType) {
158
159 auto fetchLocalPoint =
160 [] (const KisBezierPatch &patch,
165
166 const qreal handleLength = kisDistance(patch.points[c0], patch.points[c1]);
167 const qreal totalLength = handleLength +
168 kisDistance(patch.points[c1], patch.points[c2]) +
169 kisDistance(patch.points[c2], patch.points[c3]);
170
171 return KisAlgebra2D::lerp(patch.originalRect.topLeft(), patch.originalRect.topRight(),
172 handleLength / totalLength);
173 };
174
175 if (dstRect.contains(patch.points[controlType])) {
176 QPointF localPoint;
177
178 switch (controlType) {
180 localPoint = patch.originalRect.topLeft();
181 break;
183 localPoint = fetchLocalPoint(patch,
188 break;
189 }
191 localPoint = fetchLocalPoint(patch,
196 break;
198 localPoint = patch.originalRect.topRight();
199 break;
201 localPoint = fetchLocalPoint(patch,
206 break;
208 localPoint = fetchLocalPoint(patch,
213
214 break;
216 localPoint = patch.originalRect.bottomLeft();
217 break;
219 localPoint = fetchLocalPoint(patch,
224 break;
226 localPoint = fetchLocalPoint(patch,
231 break;
233 localPoint = patch.originalRect.bottomRight();
234 break;
236 localPoint = fetchLocalPoint(patch,
241 break;
243 localPoint = fetchLocalPoint(patch,
248
249 break;
250 }
251
252 KisAlgebra2D::accumulateBounds(localPoint, &stepRect);
253 }
254 };
255
256 for (auto it = beginPatches(); it != endPatches(); ++it) {
257 tryAddHandle(*it, KisBezierPatch::TL);
258 tryAddHandle(*it, KisBezierPatch::TL_HC);
259 tryAddHandle(*it, KisBezierPatch::TL_VC);
260
261 tryAddHandle(*it, KisBezierPatch::TR);
262 tryAddHandle(*it, KisBezierPatch::TR_HC);
263 tryAddHandle(*it, KisBezierPatch::TR_VC);
264
265 tryAddHandle(*it, KisBezierPatch::BL);
266 tryAddHandle(*it, KisBezierPatch::BL_HC);
267 tryAddHandle(*it, KisBezierPatch::BL_VC);
268
269 tryAddHandle(*it, KisBezierPatch::BR);
270 tryAddHandle(*it, KisBezierPatch::BR_HC);
271 tryAddHandle(*it, KisBezierPatch::BR_VC);
272 }
273 }
274
275 KisSampleRectIterator dstRectSampler(sampleRect);
276 KisBezierPatch patch = *beginPatches();
277 KisBezierPatchParamToSourceSampler patchSampler(patch);
278
281 int hitPoints = 0;
282
283 while (1) {
284 for (int i = 0; i < 10; i++) {
285 const QPointF dstPoint = *dstRectSampler++;
286
287 if (patch.dstBoundingRect().contains(dstPoint)) {
288 const QPointF localPoint = patch.globalToLocal(dstPoint);
289 if (unitRect.contains(localPoint)) {
290 KisAlgebra2D::accumulateBounds(patchSampler.point(localPoint), &stepRect);
291 hitPoints++;
292 continue;
293 }
294 }
295
296 {
297 QPointF localPoint;
298 auto it = hitTestPatchImpl(dstPoint, &localPoint);
299 if (it != endPatches()) {
300 patch = *it;
301 patchSampler = KisBezierPatchParamToSourceSampler(patch);
302
303 KisAlgebra2D::accumulateBounds(patchSampler.point(localPoint), &stepRect);
304 hitPoints++;
305 }
306 }
307 }
308
309 QRect alignedRect = stepRect.toAlignedRect();
310
311 if (hitPoints > 20 && !alignedRect.isEmpty() && alignedRect == result) {
312 break;
313 }
314
315 result = alignedRect;
316
317 if (dstRectSampler.numSamples() > qMin(2000, samplesLimit)) {
322 if (!result.isEmpty()) {
323 qWarning() << "KisBezierTransformMesh::approxNeedRect: the algorithm hasn't converged!"
324 << ppVar(hitPoints) << ppVar(stepRect) << ppVar(alignedRect) << ppVar(result);
325 }
326 break;
327 }
328 }
329
330 return result;
331}
332
334{
335 QRect result;
336
337 const QRect affectedPatches = hitTestPatchInSourceSpace(rc);
338
339 for (int row = affectedPatches.top(); row <= affectedPatches.bottom(); row++) {
340 for (int column = affectedPatches.left(); column <= affectedPatches.right(); column++) {
341 const KisBezierPatch patch = *find(PatchIndex(column, row));
342 const QRectF srcRect = QRectF(rc) & patch.srcBoundingRect();
343 const QRectF paramRect = calcTightSrcRectRangeInParamSpace(patch, srcRect, 0.1);
344
345 KisSampleRectIterator paramRectSampler(paramRect);
346 QRect patchResultRect;
347 QRectF stepRect;
348
349 while (1) {
350 for (int i = 0; i < 10; i++) {
351 const QPointF sampledParamPoint = *paramRectSampler++;
352 const QPointF globalPoint = patch.localToGlobal(sampledParamPoint);
353 KisAlgebra2D::accumulateBounds(globalPoint, &stepRect);
354 }
355
356 const QRect alignedRect = stepRect.toAlignedRect();
357
358 if (!alignedRect.isEmpty() && alignedRect == patchResultRect) {
359 break;
360 }
361
362 patchResultRect = alignedRect;
363
364 if (paramRectSampler.numSamples() > 2000) {
365 qWarning() << "KisBezierTransformMesh::approxChangeRect: the algorithm hasn't converged!"
366 << ppVar(result) << ppVar(patchResultRect) << ppVar(stepRect);
367 break;
368 }
369 }
370
371 result |= patchResultRect;
372 }
373 }
374
375 return result;
376}
377
378
383QRectF KisBezierTransformMesh::calcTightSrcRectRangeInParamSpace(const KisBezierPatch &patch, const QRectF &srcSpaceRect, qreal srcPrecision)
384{
387
388 KIS_ASSERT_RECOVER_NOOP(patch.srcBoundingRect().contains(srcSpaceRect));
389
391
392 auto xSampler = [sampler] (qreal xParam) -> Range {
393 return sampler.xRange(xParam);
394 };
395
396 auto ySampler = [sampler] (qreal yParam) -> Range {
397 return sampler.yRange(yParam);
398 };
399
400 Range externalRangeX;
401 Range internalRangeX;
402
403 Range externalRangeY;
404 Range internalRangeY;
405
406 std::tie(externalRangeX, internalRangeX) =
407 calcTightSrcRectRangeInParamSpace1D({0.0, 1.0},
408 Range::fromRectX(patch.originalRect),
409 Range::fromRectX(srcSpaceRect),
410 xSampler, srcPrecision);
411
412 std::tie(externalRangeY, internalRangeY) =
413 calcTightSrcRectRangeInParamSpace1D({0.0, 1.0},
414 Range::fromRectY(patch.originalRect),
415 Range::fromRectY(srcSpaceRect),
416 ySampler, srcPrecision);
417
418 return Range::makeRectF(externalRangeX, externalRangeY);
419}
420
421#include <kis_dom_utils.h>
422
423void KisBezierTransformMeshDetail::saveValue(QDomElement *parent, const QString &tag, const KisBezierTransformMesh &mesh)
424{
425 QDomDocument doc = parent->ownerDocument();
426 QDomElement e = doc.createElement(tag);
427 parent->appendChild(e);
428
429 e.setAttribute("type", "transform-mesh");
430
431 KisDomUtils::saveValue(&e, "size", mesh.m_size);
432 KisDomUtils::saveValue(&e, "srcRect", mesh.m_originalRect);
433 KisDomUtils::saveValue(&e, "columns", mesh.m_columns);
434 KisDomUtils::saveValue(&e, "rows", mesh.m_rows);
435 KisDomUtils::saveValue(&e, "nodes", mesh.m_nodes);
436}
437
439{
440 if (!KisDomUtils::Private::checkType(e, "transform-mesh")) return false;
441
442 mesh->m_columns.clear();
443 mesh->m_rows.clear();
444 mesh->m_nodes.clear();
445
446 KisDomUtils::loadValue(e, "size", &mesh->m_size);
447 KisDomUtils::loadValue(e, "srcRect", &mesh->m_originalRect);
448 KisDomUtils::loadValue(e, "columns", &mesh->m_columns);
449 KisDomUtils::loadValue(e, "rows", &mesh->m_rows);
450 KisDomUtils::loadValue(e, "nodes", &mesh->m_nodes);
451
452 return true;
453}
QPointF dstPoint
qreal distance(const QPointF &p1, const QPointF &p2)
std::vector< qreal > m_rows
patch_iterator endPatches()
std::vector< qreal > m_columns
patch_iterator beginPatches()
control_point_iterator find(const ControlPointIndex &index)
std::vector< Node > m_nodes
void sampleRegularGrid(QSize &gridSize, QVector< QPointF > &origPoints, QVector< QPointF > &transfPoints, const QPointF &dstStep) const
QPointF localToGlobal(const QPointF &pt) const
QRectF dstBoundingRect() const
std::array< QPointF, 12 > points
QPointF globalToLocal(const QPointF &pt) const
QRectF srcBoundingRect() const
static QRectF calcTightSrcRectRangeInParamSpace(const KisBezierPatch &patch, const QRectF &srcSpaceRect, qreal srcPrecision)
patch_const_iterator hitTestPatchImpl(const QPointF &pt, QPointF *localPointResult=0) const
void transformMesh(const QPoint &srcQImageOffset, const QImage &srcImage, const QPoint &dstQImageOffset, QImage *dstImage) const
static void transformPatch(const KisBezierPatch &patch, const QPoint &srcQImageOffset, const QImage &srcImage, const QPoint &dstQImageOffset, QImage *dstImage)
PatchIndex hitTestPatch(const QPointF &pt, QPointF *localPointResult=0) const
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define ppVar(var)
Definition kis_debug.h:155
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
void iterateThroughGrid(PolygonOp &polygonOp, IndexesOp &indexesOp, const QSize &gridSize, const QVector< QPointF > &originalPoints, const QVector< QPointF > &transformedPoints)
QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc)
Point lerp(const Point &pt1, const Point &pt2, qreal t)
void accumulateBounds(const Point &pt, Rect *bounds)
KRITAIMAGE_EXPORT bool loadValue(const QDomElement &parent, KisBezierTransformMesh *mesh)
KRITAIMAGE_EXPORT void saveValue(QDomElement *parent, const QString &tag, const KisBezierTransformMesh &mesh)
QPointF calculateLocalPos(const std::array< QPointF, 12 > &points, const QPointF &globalPoint)
calculates local (u,v) coordinates of the patch corresponding to globalPoint
std::pair< Range, Range > calcTightSrcRectRangeInParamSpace1D(const Range &searchParamRange, const Range &searchSrcRange, const Range &rect, Func func, qreal srcPrecision, std::optional< Range > squeezeRange=std::nullopt)
bool checkType(const QDomElement &e, const QString &expectedType)
void saveValue(QDomElement *parent, const QString &tag, const QSize &size)
bool loadValue(const QDomElement &e, float *v)
QPointF point(qreal xParam, qreal yParam) const