Commit 9c8df73c authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implemented Liquify Transoform Worker

It shares the code with the cage transform almost entirely
parent 9cd95e9c
......@@ -203,6 +203,7 @@ set(kritaimage_LIB_SRCS
# bsplines/kis_nu_bspline_2d.cpp
kis_warptransform_worker.cc
kis_cage_transform_worker.cpp
kis_liquify_transform_worker.cpp
kis_green_coordinates_math.cpp
kis_algebra_2d.cpp
kis_transparency_mask.cc
......
......@@ -167,6 +167,28 @@ inline void accumulateBounds(const Point &pt, Rect *bounds)
}
}
template <class Point, class Rect>
inline Point clampPoint(Point pt, const Rect &bounds)
{
if (pt.x() > bounds.right()) {
pt.rx() = bounds.right();
}
if (pt.x() < bounds.left()) {
pt.rx() = bounds.left();
}
if (pt.y() > bounds.bottom()) {
pt.ry() = bounds.bottom();
}
if (pt.y() < bounds.top()) {
pt.ry() = bounds.top();
}
return pt;
}
}
#endif /* __KIS_ALGEBRA_2D_H */
......@@ -20,7 +20,6 @@
#include "kis_grid_interpolation_tools.h"
#include "kis_green_coordinates_math.h"
#include "kis_algebra_2d.h"
#include <QPainter>
......@@ -28,8 +27,6 @@
#include "kis_selection.h"
#include "kis_painter.h"
#include "kis_four_point_interpolator_forward.h"
struct KisCageTransformWorker::Private
{
......@@ -69,31 +66,12 @@ struct KisCageTransformWorker::Private
QVector<QPointF> calculateTransformedPoints();
/**
* A-----B The polygons will be in the following order:
* | |
* | | polygon << A << B << D << C;
* C-----D
*/
inline QVector<int> calculateCellIndexes(int col, int row);
inline QVector<int> calculateMappedIndexes(int col, int row,
int *numExistingPoints);
inline int pointToIndex(const QPoint &cellPt);
int tryGetValidIndex(const QPoint &cellPt);
bool getOrthogonalPointApproximation(const QPoint &cellPt,
const QVector<QPointF> &transformedPoints,
QPointF *srcPoint,
QPointF *dstPoint);
inline QPoint pointIndexToColRow(QPoint baseColRow, int index);
template <class PolygonOp>
void iterateThroughGrid(PolygonOp polygonOp,
const QVector<QPointF> &transformedPoints);
struct MapIndexesOp;
};
KisCageTransformWorker::KisCageTransformWorker(KisPaintDeviceSP dev,
......@@ -126,12 +104,6 @@ void KisCageTransformWorker::setTransformedCage(const QVector<QPointF> &transfor
struct PointsFetcherOp
{
static const QPointF invalidPoint;
static inline bool isPointValid(const QPointF &pt) {
return pt.x() > -1e10 && pt.y() > -1e10;
}
PointsFetcherOp(const QPolygonF &cagePolygon)
: m_cagePolygon(cagePolygon),
m_numValidPoints(0)
......@@ -172,8 +144,6 @@ struct PointsFetcherOp
int m_numValidPoints;
};
const QPointF PointsFetcherOp::invalidPoint(-1e12, -1e12);
void KisCageTransformWorker::prepareTransform()
{
if (m_d->origCage.size() < 3) return;
......@@ -232,29 +202,13 @@ QVector<QPointF> KisCageTransformWorker::Private::calculateTransformedPoints()
return transformedPoints;
}
inline QVector<int> KisCageTransformWorker::Private::
calculateCellIndexes(int col, int row)
{
const int tl = col + row * gridSize.width();
const int tr = tl + 1;
const int bl = tl + gridSize.width();
const int br = bl + 1;
QVector<int> cellIndexes;
cellIndexes << tl;
cellIndexes << tr;
cellIndexes << br;
cellIndexes << bl;
return cellIndexes;
}
inline QVector<int> KisCageTransformWorker::Private::
calculateMappedIndexes(int col, int row,
int *numExistingPoints)
{
*numExistingPoints = 0;
QVector<int> cellIndexes = calculateCellIndexes(col, row);
QVector<int> cellIndexes =
GridIterationTools::calculateCellIndexes(col, row, gridSize);
for (int i = 0; i < 4; i++) {
cellIndexes[i] = allToValidPointsMap[cellIndexes[i]];
......@@ -264,12 +218,7 @@ calculateMappedIndexes(int col, int row,
return cellIndexes;
}
inline int KisCageTransformWorker::Private::
pointToIndex(const QPoint &cellPt)
{
return cellPt.x() +
cellPt.y() * gridSize.width();
}
int KisCageTransformWorker::Private::
tryGetValidIndex(const QPoint &cellPt)
......@@ -281,194 +230,39 @@ tryGetValidIndex(const QPoint &cellPt)
cellPt.y() >= 0 &&
cellPt.x() < gridSize.width() - 1 &&
cellPt.y() < gridSize.height() - 1 &&
(index = allToValidPointsMap[pointToIndex(cellPt)]) >= 0, index;
(index = allToValidPointsMap[GridIterationTools::pointToIndex(cellPt, gridSize)]) >= 0, index;
}
struct PointExtension {
int near;
int far;
};
bool KisCageTransformWorker::Private::
getOrthogonalPointApproximation(const QPoint &cellPt,
const QVector<QPointF> &transformedPoints,
QPointF *srcPoint,
QPointF *dstPoint)
{
QVector<PointExtension> extensionPoints;
PointExtension ext;
// left
if ((ext.near = tryGetValidIndex(cellPt + QPoint(-1, 0))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(-2, 0))) >= 0) {
extensionPoints << ext;
}
// top
if ((ext.near = tryGetValidIndex(cellPt + QPoint(0, -1))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(0, -2))) >= 0) {
extensionPoints << ext;
}
// right
if ((ext.near = tryGetValidIndex(cellPt + QPoint(1, 0))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(2, 0))) >= 0) {
extensionPoints << ext;
}
// bottom
if ((ext.near = tryGetValidIndex(cellPt + QPoint(0, 1))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(0, 2))) >= 0) {
struct KisCageTransformWorker::Private::MapIndexesOp {
extensionPoints << ext;
MapIndexesOp(KisCageTransformWorker::Private *d)
: m_d(d),
m_srcCagePolygon(QPolygonF(m_d->origCage))
{
}
if (extensionPoints.isEmpty()) {
// top-left
if ((ext.near = tryGetValidIndex(cellPt + QPoint(-1, -1))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(-2, -2))) >= 0) {
extensionPoints << ext;
}
// top-right
if ((ext.near = tryGetValidIndex(cellPt + QPoint(1, -1))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(2, -2))) >= 0) {
extensionPoints << ext;
}
// bottom-right
if ((ext.near = tryGetValidIndex(cellPt + QPoint(1, 1))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(2, 2))) >= 0) {
extensionPoints << ext;
}
// bottom-left
if ((ext.near = tryGetValidIndex(cellPt + QPoint(-1, 1))) >= 0 &&
(ext.far = tryGetValidIndex(cellPt + QPoint(-2, 2))) >= 0) {
inline QVector<int> calculateMappedIndexes(int col, int row,
int *numExistingPoints) const {
extensionPoints << ext;
}
return m_d->calculateMappedIndexes(col, row, numExistingPoints);
}
if (extensionPoints.isEmpty()) {
return false;
inline int tryGetValidIndex(const QPoint &cellPt) const {
return m_d->tryGetValidIndex(cellPt);
}
int numResultPoints = 0;
*srcPoint = allSrcPoints[pointToIndex(cellPt)];
*dstPoint = QPointF();
foreach (const PointExtension &ext, extensionPoints) {
QPointF near = transformedPoints[ext.near];
QPointF far = transformedPoints[ext.far];
QPointF nearSrc = validPoints[ext.near];
QPointF farSrc = validPoints[ext.far];
QPointF base1 = nearSrc - farSrc;
QPointF base2 = near - far;
QPointF pt = near +
KisAlgebra2D::transformAsBase(*srcPoint - nearSrc, base1, base2);
*dstPoint += pt;
numResultPoints++;
inline QPointF getSrcPointForce(const QPoint &cellPt) const {
return m_d->allSrcPoints[GridIterationTools::pointToIndex(cellPt, m_d->gridSize)];
}
*dstPoint /= numResultPoints;
return true;
}
inline QPoint KisCageTransformWorker::Private::
pointIndexToColRow(QPoint baseColRow, int index)
{
static QVector<QPoint> pointOffsets;
if (pointOffsets.isEmpty()) {
pointOffsets << QPoint(0,0);
pointOffsets << QPoint(1,0);
pointOffsets << QPoint(1,1);
pointOffsets << QPoint(0,1);
inline const QPolygonF srcCropPolygon() const {
return m_srcCagePolygon;
}
return baseColRow + pointOffsets[index];
}
template <class PolygonOp>
void KisCageTransformWorker::Private::
iterateThroughGrid(PolygonOp polygonOp,
const QVector<QPointF> &transformedPoints)
{
QPolygonF cageDstPolygon(transfCage);
QPolygonF cageSrcPolygon(origCage);
QVector<int> polygonPoints(4);
for (int row = 0; row < gridSize.height() - 1; row++) {
for (int col = 0; col < gridSize.width() - 1; col++) {
int numExistingPoints = 0;
polygonPoints = calculateMappedIndexes(col, row, &numExistingPoints);
if (numExistingPoints == 0) continue;
if (numExistingPoints < 4) {
QPolygonF srcPolygon;
QPolygonF dstPolygon;
for (int i = 0; i < 4; i++) {
const int index = polygonPoints[i];
if (index >= 0) {
srcPolygon << validPoints[index];
dstPolygon << transformedPoints[index];
} else {
QPoint cellPt = pointIndexToColRow(QPoint(col, row), i);
QPointF srcPoint;
QPointF dstPoint;
bool result =
getOrthogonalPointApproximation(cellPt,
transformedPoints,
&srcPoint,
&dstPoint);
if (!result) {
//qDebug() << "*NOT* found any valid point" << allSrcPoints[pointToIndex(cellPt)] << "->" << ppVar(pt);
break;
} else {
srcPolygon << srcPoint;
dstPolygon << dstPoint;
}
}
}
if (dstPolygon.size() == 4) {
QPolygonF srcClipPolygon(srcPolygon.intersected(cageSrcPolygon));
KisFourPointInterpolatorForward forwardTransform(srcPolygon, dstPolygon);
for (int i = 0; i < srcClipPolygon.size(); i++) {
const QPointF newPt = forwardTransform.map(srcClipPolygon[i]);
srcClipPolygon[i] = newPt;
}
polygonOp(srcPolygon, dstPolygon, srcClipPolygon);
}
} else {
QPolygonF srcPolygon;
QPolygonF dstPolygon;
for (int i = 0; i < 4; i++) {
const int index = polygonPoints[i];
srcPolygon << validPoints[index];
dstPolygon << transformedPoints[index];
}
polygonOp(srcPolygon, dstPolygon);
}
}
}
}
KisCageTransformWorker::Private *m_d;
QPolygonF m_srcCagePolygon;
};
void KisCageTransformWorker::run()
{
......@@ -495,7 +289,12 @@ void KisCageTransformWorker::run()
}
GridIterationTools::PaintDevicePolygonOp polygonOp(srcDev, tempDevice);
m_d->iterateThroughGrid(polygonOp, transformedPoints);
Private::MapIndexesOp indexesOp(m_d.data());
GridIterationTools::iterateThroughGrid
<GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
m_d->gridSize,
m_d->validPoints,
transformedPoints);
QRect rect = tempDevice->extent();
KisPainter gc(m_d->dev);
......@@ -550,7 +349,12 @@ QImage KisCageTransformWorker::runOnQImage(QPointF *newOffset)
}
GridIterationTools::QImagePolygonOp polygonOp(m_d->srcImage, tempImage, m_d->srcImageOffset, dstQImageOffset);
m_d->iterateThroughGrid(polygonOp, transformedPoints);
Private::MapIndexesOp indexesOp(m_d.data());
GridIterationTools::iterateThroughGrid
<GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
m_d->gridSize,
m_d->validPoints,
transformedPoints);
{
QPainter gc(&dstImage);
......
......@@ -24,6 +24,8 @@
#include <QImage>
#include "kis_algebra_2d.h"
#include "kis_four_point_interpolator_forward.h"
#include "kis_four_point_interpolator_backward.h"
#include "kis_iterator_ng.h"
#include "kis_random_sub_accessor.h"
......@@ -265,10 +267,8 @@ struct QImagePolygonOp
QPoint srcPointI = srcPoint.toPoint();
QPoint dstPointI = dstPoint.toPoint();
srcPointI.rx() = qBound(m_dstImageRect.x(), srcPointI.x(), m_dstImageRect.right());
srcPointI.ry() = qBound(m_dstImageRect.y(), srcPointI.y(), m_dstImageRect.bottom());
dstPointI.rx() = qBound(m_srcImageRect.x(), dstPointI.x(), m_srcImageRect.right());
dstPointI.ry() = qBound(m_srcImageRect.y(), dstPointI.y(), m_srcImageRect.bottom());
srcPointI = KisAlgebra2D::clampPoint(srcPointI, m_dstImageRect);
dstPointI = KisAlgebra2D::clampPoint(dstPointI, m_srcImageRect);
m_dstImage.setPixel(srcPointI, m_srcImage.pixel(dstPointI));
}
......@@ -299,6 +299,277 @@ struct QImagePolygonOp
QRect m_dstImageRect;
};
/*************************************************************/
/* Iteration through precalculated grid */
/*************************************************************/
/**
* A-----B The polygons will be in the following order:
* | |
* | | polygon << A << B << D << C;
* C-----D
*/
inline QVector<int> calculateCellIndexes(int col, int row, const QSize &gridSize)
{
const int tl = col + row * gridSize.width();
const int tr = tl + 1;
const int bl = tl + gridSize.width();
const int br = bl + 1;
QVector<int> cellIndexes;
cellIndexes << tl;
cellIndexes << tr;
cellIndexes << br;
cellIndexes << bl;
return cellIndexes;
}
inline int pointToIndex(const QPoint &cellPt, const QSize &gridSize)
{
return cellPt.x() +
cellPt.y() * gridSize.width();
}
namespace Private {
inline QPoint pointPolygonIndexToColRow(QPoint baseColRow, int index)
{
static QVector<QPoint> pointOffsets;
if (pointOffsets.isEmpty()) {
pointOffsets << QPoint(0,0);
pointOffsets << QPoint(1,0);
pointOffsets << QPoint(1,1);
pointOffsets << QPoint(0,1);
}
return baseColRow + pointOffsets[index];
}
struct PointExtension {
int near;
int far;
};
}
template <class IndexesOp>
bool getOrthogonalPointApproximation(const QPoint &cellPt,
const QVector<QPointF> &originalPoints,
const QVector<QPointF> &transformedPoints,
IndexesOp indexesOp,
QPointF *srcPoint,
QPointF *dstPoint)
{
QVector<Private::PointExtension> extensionPoints;
Private::PointExtension ext;
// left
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 0))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 0))) >= 0) {
extensionPoints << ext;
}
// top
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -1))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -2))) >= 0) {
extensionPoints << ext;
}
// right
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 0))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 0))) >= 0) {
extensionPoints << ext;
}
// bottom
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 1))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 2))) >= 0) {
extensionPoints << ext;
}
if (extensionPoints.isEmpty()) {
// top-left
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, -1))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, -2))) >= 0) {
extensionPoints << ext;
}
// top-right
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, -1))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, -2))) >= 0) {
extensionPoints << ext;
}
// bottom-right
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 1))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 2))) >= 0) {
extensionPoints << ext;
}
// bottom-left
if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 1))) >= 0 &&
(ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 2))) >= 0) {
extensionPoints << ext;
}
}
if (extensionPoints.isEmpty()) {
return false;
}
int numResultPoints = 0;
*srcPoint = indexesOp.getSrcPointForce(cellPt);
*dstPoint = QPointF();
foreach (const Private::PointExtension &ext, extensionPoints) {
QPointF near = transformedPoints[ext.near];
QPointF far = transformedPoints[ext.far];
QPointF nearSrc = originalPoints[ext.near];
QPointF farSrc = originalPoints[ext.far];
QPointF base1 = nearSrc - farSrc;
QPointF base2 = near - far;
QPointF pt = near +
KisAlgebra2D::transformAsBase(*srcPoint - nearSrc, base1, base2);
*dstPoint += pt;
numResultPoints++;
}
*dstPoint /= numResultPoints;
return true;
}
template <class PolygonOp, class IndexesOp>
struct IncompletePolygonPolicy {
static inline bool tryProcessPolygon(int col, int row,
int numExistingPoints,
PolygonOp &polygonOp,
IndexesOp &indexesOp,
const QVector<int> &polygonPoints,
const QVector<QPointF> &originalPoints,
const QVector<QPointF> &transformedPoints)
{
if (numExistingPoints >= 4) return false;
if (numExistingPoints == 0) return true;
QPolygonF srcPolygon;
QPolygonF dstPolygon;
for (int i = 0; i < 4; i++) {
const int index = polygonPoints[i];
if (index >= 0) {
srcPolygon << originalPoints[index];
dstPolygon << transformedPoints[index];
} else {
QPoint cellPt = Private::pointPolygonIndexToColRow(QPoint(col, row), i);
QPointF srcPoint;
QPointF dstPoint;
bool result =
getOrthogonalPointApproximation(cellPt,
originalPoints,
transformedPoints,
indexesOp,
&srcPoint,
&dstPoint);
if (!result) {
//qDebug() << "*NOT* found any valid point" << allSrcPoints[pointToIndex(cellPt)] << "->" << ppVar(pt);
break;
} else {
srcPolygon << srcPoint;
dstPolygon << dstPoint;
}
}
}
if (dstPolygon.size() == 4) {
QPolygonF srcClipPolygon(srcPolygon.intersected(indexesOp.srcCropPolygon()));
KisFourPointInterpolatorForward forwardTransform(srcPolygon, dstPolygon);
for (int i = 0; i < srcClipPolygon.size(); i++) {
const QPointF newPt = forwardTransform.map(srcClipPolygon[i]);
srcClipPolygon[i] = newPt;
}
polygonOp(srcPolygon, dstPolygon, srcClipPolygon);
}
return true;