Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_cage_transform_worker.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
8
11
12#include <QPainter>
13
14#include "KoColor.h"
15#include "kis_selection.h"
16#include "kis_painter.h"
17#include "kis_image.h"
18#include "krita_utils.h"
19
20#include <qnumeric.h>
21
22struct Q_DECL_HIDDEN KisCageTransformWorker::Private
23{
24 Private(const QVector<QPointF> &_origCage,
25 KoUpdater *_progress,
26 int _pixelPrecision)
27 : origCage(_origCage),
28 progress(_progress),
29 pixelPrecision(_pixelPrecision)
30 {
31 }
32
33 QRect srcBounds;
34
35 QImage srcImage;
37
42
45
51
53
54 QSize gridSize;
55
56 bool isGridEmpty() const {
57 return allSrcPoints.isEmpty();
58 }
59
60
62
63 inline QVector<int> calculateMappedIndexes(int col, int row,
64 int *numExistingPoints);
65
66 int tryGetValidIndex(const QPoint &cellPt);
67
68 struct MapIndexesOp;
69};
70
71KisCageTransformWorker::KisCageTransformWorker(const QRect &deviceNonDefaultRegion,
72 const QVector<QPointF> &origCage,
73 KoUpdater *progress,
74 int pixelPrecision)
75 : m_d(new Private(origCage, progress, pixelPrecision))
76{
77 m_d->srcBounds = deviceNonDefaultRegion;
78}
79
81 const QPointF &srcImageOffset,
82 const QVector<QPointF> &origCage,
83 KoUpdater *progress,
84 int pixelPrecision)
85 : m_d(new Private(origCage, progress, pixelPrecision))
86{
87 m_d->srcImage = srcImage;
88 m_d->srcImageOffset = srcImageOffset;
89 m_d->srcBounds = QRectF(m_d->srcImageOffset, m_d->srcImage.size()).toAlignedRect();
90}
91
95
97{
98 m_d->transfCage = transformedCage;
99}
100
102{
103 PointsFetcherOp(const QPolygonF &cagePolygon)
104 : m_cagePolygon(cagePolygon),
106 {
108 }
109
110 inline void processPoint(int col, int row,
111 int prevCol, int prevRow,
112 int colIndex, int rowIndex) {
113
114 Q_UNUSED(prevCol);
115 Q_UNUSED(prevRow);
116 Q_UNUSED(colIndex);
117 Q_UNUSED(rowIndex);
118
119 QPointF pt(col, row);
120
121 if (m_cagePolygon.containsPoint(pt, Qt::OddEvenFill)) {
123
124 m_points << pt;
125 m_pointValid << true;
127 } else {
128 m_points << pt;
129 m_pointValid << false;
130 }
131 }
132
133 inline void nextLine() {
134 }
135
138 QPolygonF m_cagePolygon;
141};
142
144{
145 if (m_d->origCage.size() < 3) return;
146
147 const QPolygonF srcPolygon(m_d->origCage);
148
149 QRect srcBounds = m_d->srcBounds;
150 srcBounds &= srcPolygon.boundingRect().toAlignedRect();
151
152 // no need to process empty devices
153 if (srcBounds.isEmpty()) return;
154 m_d->gridSize =
156
157 PointsFetcherOp pointsOp(srcPolygon);
158 GridIterationTools::processGrid(pointsOp, srcBounds, m_d->pixelPrecision);
159
160 const int numPoints = pointsOp.m_points.size();
161 KIS_ASSERT_RECOVER_RETURN(numPoints == m_d->gridSize.width() * m_d->gridSize.height());
162
163 m_d->allSrcPoints = pointsOp.m_points;
164 m_d->allToValidPointsMap.resize(pointsOp.m_points.size());
165 m_d->validPoints.resize(pointsOp.m_numValidPoints);
166
167 {
168 int validIdx = 0;
169 for (int i = 0; i < numPoints; i++) {
170 const QPointF &pt = pointsOp.m_points[i];
171 const bool pointValid = pointsOp.m_pointValid[i];
172
173 if (pointValid) {
174 m_d->validPoints[validIdx] = pt;
175 m_d->allToValidPointsMap[i] = validIdx;
176 validIdx++;
177 } else {
178 m_d->allToValidPointsMap[i] = -1;
179 }
180 }
181 KIS_ASSERT_RECOVER_NOOP(validIdx == m_d->validPoints.size());
182 }
183
184 m_d->cage.precalculateGreenCoordinates(m_d->origCage, m_d->validPoints);
185}
186
187QVector<QPointF> KisCageTransformWorker::Private::calculateTransformedPoints()
188{
189 cage.generateTransformedCageNormals(transfCage);
190
191 const int numValidPoints = validPoints.size();
192 QVector<QPointF> transformedPoints(numValidPoints);
193
194 for (int i = 0; i < numValidPoints; i++) {
195 transformedPoints[i] = cage.transformedPoint(i, transfCage);
196
197 if (qIsNaN(transformedPoints[i].x()) ||
198 qIsNaN(transformedPoints[i].y())) {
199 warnKrita << "WARNING: One grid point has been removed from consideration" << validPoints[i];
200 transformedPoints[i] = validPoints[i];
201 }
202
203 }
204
205 return transformedPoints;
206}
207
208inline QVector<int> KisCageTransformWorker::Private::
209calculateMappedIndexes(int col, int row,
210 int *numExistingPoints)
211{
212 *numExistingPoints = 0;
213 QVector<int> cellIndexes =
215
216 for (int i = 0; i < 4; i++) {
217 cellIndexes[i] = allToValidPointsMap[cellIndexes[i]];
218 *numExistingPoints += cellIndexes[i] >= 0;
219 }
220
221 return cellIndexes;
222}
223
224
225
226int KisCageTransformWorker::Private::
227tryGetValidIndex(const QPoint &cellPt)
228{
229 int index = -1;
230 if (cellPt.x() >= 0 &&
231 cellPt.y() >= 0 &&
232 cellPt.x() < gridSize.width() - 1 &&
233 cellPt.y() < gridSize.height() - 1) {
234
235 index = allToValidPointsMap[GridIterationTools::pointToIndex(cellPt, gridSize)];
236 }
237
238 return index;
239}
240
241
243
244 MapIndexesOp(KisCageTransformWorker::Private *d)
245 : m_d(d),
246 m_srcCagePolygon(QPolygonF(m_d->origCage))
247 {
248 }
249
250 inline QVector<int> calculateMappedIndexes(int col, int row,
251 int *numExistingPoints) const {
252
253 return m_d->calculateMappedIndexes(col, row, numExistingPoints);
254 }
255
256 inline int tryGetValidIndex(const QPoint &cellPt) const {
257 return m_d->tryGetValidIndex(cellPt);
258 }
259
260 inline QPointF getSrcPointForce(const QPoint &cellPt) const {
261 return m_d->allSrcPoints[GridIterationTools::pointToIndex(cellPt, m_d->gridSize)];
262 }
263
264 inline const QPolygonF srcCropPolygon() const {
265 return m_srcCagePolygon;
266 }
267
270};
271
273{
274 const qreal margin = 0.30;
275
276 QVector<QPointF> cageSamplePoints;
277
278 const int minStep = 3;
279 const int maxSamples = 200;
280
281 const int totalPixels = rc.width() * rc.height();
282 const int realStep = qMax(minStep, totalPixels / maxSamples);
283 const QPolygonF cagePolygon(m_d->origCage);
284
285 for (int i = 0; i < totalPixels; i += realStep) {
286 const int x = rc.x() + i % rc.width();
287 const int y = rc.y() + i / rc.width();
288
289 const QPointF pt(x, y);
290 if (cagePolygon.containsPoint(pt, Qt::OddEvenFill)) {
291 cageSamplePoints << pt;
292 }
293 }
294
295 if (cageSamplePoints.isEmpty()) {
296 return rc;
297 }
298
300 cage.precalculateGreenCoordinates(m_d->origCage, cageSamplePoints);
302
303 const int numValidPoints = cageSamplePoints.size();
304 QVector<QPointF> transformedPoints(numValidPoints);
305
306 int failedPoints = 0;
307
308 for (int i = 0; i < numValidPoints; i++) {
309 transformedPoints[i] = cage.transformedPoint(i, m_d->transfCage);
310
311 if (qIsNaN(transformedPoints[i].x()) ||
312 qIsNaN(transformedPoints[i].y())) {
313
314 transformedPoints[i] = cageSamplePoints[i];
315 failedPoints++;
316 }
317 }
318
319 QRect resultRect =
320 KisAlgebra2D::approximateRectFromPoints(transformedPoints).toAlignedRect();
321
322 return KisAlgebra2D::blowRect(resultRect | rc, margin);
323}
324
325QRect KisCageTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
326{
327 Q_UNUSED(rc);
328 return fullBounds;
329}
330
332{
333 if (m_d->isGridEmpty()) return;
334
335 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->origCage.size() >= 3);
336 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->origCage.size() == m_d->transfCage.size());
337 KIS_SAFE_ASSERT_RECOVER_RETURN(*srcDevice->colorSpace() == *dstDevice->colorSpace());
338
339 QVector<QPointF> transformedPoints = m_d->calculateTransformedPoints();
340
341 KisPaintDeviceSP tempDevice = new KisPaintDevice(dstDevice->colorSpace());
342
343 {
344 KisSelectionSP selection = new KisSelection();
345
346 KisPainter painter(selection->pixelSelection());
347 painter.setPaintColor(KoColor(Qt::black, selection->pixelSelection()->colorSpace()));
348 painter.setAntiAliasPolygonFill(true);
351
352 painter.paintPolygon(m_d->origCage);
353
354 dstDevice->clearSelection(selection);
355 }
356
357 GridIterationTools::PaintDevicePolygonOp polygonOp(srcDevice, tempDevice);
358 Private::MapIndexesOp indexesOp(m_d.data());
360 <GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
361 m_d->gridSize,
362 m_d->validPoints,
363 transformedPoints);
364
365 QRect rect = tempDevice->extent();
366 KisPainter gc(dstDevice);
367 gc.bitBlt(rect.topLeft(), tempDevice, rect);
368}
369
370QImage KisCageTransformWorker::runOnQImage(QPointF *newOffset)
371{
372 if (m_d->isGridEmpty()) return QImage();
373
374 KIS_ASSERT_RECOVER(m_d->origCage.size() >= 3 &&
375 m_d->origCage.size() == m_d->transfCage.size()) {
376 return QImage();
377 }
378
379 KIS_ASSERT_RECOVER(!m_d->srcImage.isNull()) {
380 return QImage();
381 }
382
383 KIS_ASSERT_RECOVER(m_d->srcImage.format() == QImage::Format_ARGB32) {
384 return QImage();
385 }
386
387 QVector<QPointF> transformedPoints = m_d->calculateTransformedPoints();
388
389 QRectF dstBounds;
390 Q_FOREACH (const QPointF &pt, transformedPoints) {
391 KisAlgebra2D::accumulateBounds(pt, &dstBounds);
392 }
393
394 const QRectF srcBounds(m_d->srcImageOffset, m_d->srcImage.size());
395 dstBounds |= srcBounds;
396
397 QPointF dstQImageOffset = dstBounds.topLeft();
398 *newOffset = dstQImageOffset;
399
400 QRect dstBoundsI = dstBounds.toAlignedRect();
401
402
403 QImage dstImage(dstBoundsI.size(), m_d->srcImage.format());
404 dstImage.fill(0);
405
406 QImage tempImage(dstImage);
407
408 {
409 // we shouldn't create too many painters
410 QPainter gc(&dstImage);
411 gc.drawImage(-dstQImageOffset + m_d->srcImageOffset, m_d->srcImage);
412 gc.setBrush(Qt::black);
413 gc.setPen(Qt::black);
414 gc.setCompositionMode(QPainter::CompositionMode_Clear);
415 gc.drawPolygon(QPolygonF(m_d->origCage).translated(-dstQImageOffset));
416 gc.end();
417 }
418
419 GridIterationTools::QImagePolygonOp polygonOp(m_d->srcImage, tempImage, m_d->srcImageOffset, dstQImageOffset);
420 Private::MapIndexesOp indexesOp(m_d.data());
422 <GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
423 m_d->gridSize,
424 m_d->validPoints,
425 transformedPoints);
426
427 {
428 QPainter gc(&dstImage);
429 gc.drawImage(QPoint(), tempImage);
430 }
431
432 return dstImage;
433}
434
QRect extent() const
const KoColorSpace * colorSpace() const
void clearSelection(KisSelectionSP selection)
@ FillStyleForegroundColor
void setStrokeStyle(StrokeStyle strokeStyle)
Set the current brush stroke style.
void paintPolygon(const vQPointF &points)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
void setFillStyle(FillStyle fillStyle)
Set the current style with which to fill.
void setPaintColor(const KoColor &color)
void setAntiAliasPolygonFill(bool antiAliasPolygonFill)
Set whether a polygon's filled area should be anti-aliased or not. The default is true.
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define warnKrita
Definition kis_debug.h:87
void iterateThroughGrid(PolygonOp &polygonOp, IndexesOp &indexesOp, const QSize &gridSize, const QVector< QPointF > &originalPoints, const QVector< QPointF > &transformedPoints)
QVector< int > calculateCellIndexes(int col, int row, const QSize &gridSize)
int pointToIndex(const QPoint &cellPt, const QSize &gridSize)
void processGrid(ProcessCell &cellOp, const QRect &srcBounds, const int pixelPrecision)
QSize calcGridSize(const QRect &srcBounds, const int pixelPrecision)
Rect blowRect(const Rect &rect, qreal coeff)
QRect approximateRectFromPoints(const QVector< QPoint > &points)
void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt)
void accumulateBounds(const Point &pt, Rect *bounds)
int polygonDirection(const QVector< T > &polygon)
QVector< int > calculateMappedIndexes(int col, int row, int *numExistingPoints) const
const QScopedPointer< Private > m_d
KisCageTransformWorker(const QRect &deviceNonDefaultRegion, const QVector< QPointF > &origCage, KoUpdater *progress, int pixelPrecision=8)
Private(const QVector< QPointF > &_origCage, KoUpdater *_progress, int _pixelPrecision)
QVector< QPointF > calculateTransformedPoints()
void run(KisPaintDeviceSP srcDevice, KisPaintDeviceSP dstDevice)
void setTransformedCage(const QVector< QPointF > &transformedCage)
QRect approxChangeRect(const QRect &rc)
QImage runOnQImage(QPointF *newOffset)
int tryGetValidIndex(const QPoint &cellPt)
QRect approxNeedRect(const QRect &rc, const QRect &fullBounds)
QVector< int > calculateMappedIndexes(int col, int row, int *numExistingPoints)
void generateTransformedCageNormals(const QVector< QPointF > &transformedCage)
QPointF transformedPoint(int pointIndex, const QVector< QPointF > &transformedCage)
void precalculateGreenCoordinates(const QVector< QPointF > &originalCage, const QVector< QPointF > &points)
KisPixelSelectionSP pixelSelection
PointsFetcherOp(const QPolygonF &cagePolygon)
void processPoint(int col, int row, int prevCol, int prevRow, int colIndex, int rowIndex)