[calligra/krita-chili-kazakov] krita: Fixed dynamical updates of the Transform Mask
Dmitry Kazakov
dimula73 at gmail.com
Thu Nov 13 10:37:10 UTC 2014
Git commit 09488d449e118d99b9a52d32ae80e0947e87a6ab by Dmitry Kazakov.
Committed on 13/11/2014 at 10:32.
Pushed by dkazakov into branch 'krita-chili-kazakov'.
Fixed dynamical updates of the Transform Mask
This patch implements numerous fixes and refactoings:
1) Implemented KisSafeTransform. It works like a usual QTransform but
it takes maths into account. That is the transform and its reverse
are *not* defined on the entire R^2 plane. Instead the valid input
area is limited by the horizon line and nothing can be transformed
above it. KisSafeTransform takes that into account and clips the
desired rect/polygon to fit the valid area.
2) KisTransformMask::need/changeRect now uses safe trasnforms.
3) KisAsyncMerger recalculates the area of the clone's source to fetch
correct data. To fix concurrency, this extra area is taken into account
in KisCloneLayer::accessRect();
4) Implemented detailed unittests for dynamicat transform masks.
5) Added ability to store reference images of the unittest in a
separate folder (outside repository).
It consists of 3 major parts:
1) checkQImageExternal() is expected to be used to fetch data from
external folders only.
2) KRITA_UNITTESTS_DATA_DIR environment variable is used to search for
additional data sources
3) KRITA_WRITE_UNITTESTS=1 together with KRITA_UNITTESTS_DATA_DIR set
to a path will write the output of the unittest as a reference to
an external folder.
6) The testing images are stored in:
svn+ssh://svn@svn.kde.org/home/kde/trunk/tests/kritatests
CCMAIL:kimageshop at kde.org
M +1 -0 krita/image/CMakeLists.txt
M +8 -0 krita/image/kis_algebra_2d.cpp
M +14 -1 krita/image/kis_algebra_2d.h
M +12 -0 krita/image/kis_async_merger.cpp
M +36 -2 krita/image/kis_clone_layer.cpp
M +2 -0 krita/image/kis_clone_layer.h
M +1 -1 krita/image/kis_perspectivetransform_worker.cpp
A +200 -0 krita/image/kis_safe_transform.cpp [License: GPL (v2+)]
A +58 -0 krita/image/kis_safe_transform.h [License: GPL (v2+)]
M +38 -40 krita/image/kis_transform_mask.cpp
M +5 -0 krita/image/kis_transform_mask_params_interface.cpp
M +1 -0 krita/image/kis_transform_mask_params_interface.h
M +378 -14 krita/image/tests/kis_transform_mask_test.cpp
M +5 -1 krita/image/tests/kis_transform_mask_test.h
M +121 -29 krita/sdk/tests/testutil.h
http://commits.kde.org/calligra/09488d449e118d99b9a52d32ae80e0947e87a6ab
diff --git a/krita/image/CMakeLists.txt b/krita/image/CMakeLists.txt
index 25419d1..d1304b7 100644
--- a/krita/image/CMakeLists.txt
+++ b/krita/image/CMakeLists.txt
@@ -136,6 +136,7 @@ set(kritaimage_LIB_SRCS
kis_transform_mask_params_interface.cpp
kis_recalculate_transform_mask_job.cpp
kis_transform_mask_params_factory_registry.cpp
+ kis_safe_transform.cpp
kis_gradient_painter.cc
kis_iterator_ng.cpp
kis_async_merger.cpp
diff --git a/krita/image/kis_algebra_2d.cpp b/krita/image/kis_algebra_2d.cpp
index 166ed89..3bb708a 100644
--- a/krita/image/kis_algebra_2d.cpp
+++ b/krita/image/kis_algebra_2d.cpp
@@ -124,4 +124,12 @@ QPainterPath smallArrow()
return p;
}
+QRect blowRect(const QRect &rect, qreal coeff)
+{
+ int w = rect.width() * coeff;
+ int h = rect.height() * coeff;
+
+ return rect.adjusted(-w, -h, w, h);
+}
+
}
diff --git a/krita/image/kis_algebra_2d.h b/krita/image/kis_algebra_2d.h
index 8bd0e59..4f0c779 100644
--- a/krita/image/kis_algebra_2d.h
+++ b/krita/image/kis_algebra_2d.h
@@ -81,10 +81,17 @@ Point normalize(const Point &a)
*/
template <typename T>
T signPZ(T x) {
- const T zeroValue(0);
return x >= T(0) ? T(1) : T(-1);
}
+/**
+ * Usual sign() function with zero returning zero
+ */
+template <typename T>
+T signZZ(T x) {
+ return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1);
+}
+
template <class T>
T leftUnitNormal(const T &a)
{
@@ -209,6 +216,12 @@ inline Point clampPoint(Point pt, const Rect &bounds)
QPainterPath KRITAIMAGE_EXPORT smallArrow();
+/**
+ * Multiply width and height of \p rect by \p coeff keeping the
+ * center of the rectangle pinned
+ */
+QRect KRITAIMAGE_EXPORT blowRect(const QRect &rect, qreal coeff);
+
}
#endif /* __KIS_ALGEBRA_2D_H */
diff --git a/krita/image/kis_async_merger.cpp b/krita/image/kis_async_merger.cpp
index c6737ac..74e13d6 100644
--- a/krita/image/kis_async_merger.cpp
+++ b/krita/image/kis_async_merger.cpp
@@ -150,6 +150,18 @@ public:
QRegion prepareRegion(srcRect);
prepareRegion -= m_cropRect;
+ QStack<QRect> applyRects;
+ bool rectVariesFlag;
+
+ /**
+ * If a clone has complicated masks, we should prepare additional
+ * source area to ensure the rect is prepared.
+ */
+ QRect needRectOnSource = layer->needRectOnSourceForMasks(srcRect);
+ if (!needRectOnSource.isEmpty()) {
+ prepareRegion += needRectOnSource;
+ }
+
foreach(const QRect &rect, prepareRegion.rects()) {
walker.collectRects(srcLayer, rect);
merger.startMerge(walker, false);
diff --git a/krita/image/kis_clone_layer.cpp b/krita/image/kis_clone_layer.cpp
index 8f971ab..7018873 100644
--- a/krita/image/kis_clone_layer.cpp
+++ b/krita/image/kis_clone_layer.cpp
@@ -34,6 +34,9 @@
#include "kis_clone_info.h"
#include "kis_paint_layer.h"
+#include <QStack>
+#include <kis_effect_mask.h>
+
struct KisCloneLayer::Private
{
@@ -157,6 +160,28 @@ void KisCloneLayer::notifyParentVisibilityChanged(bool value)
KisLayer::notifyParentVisibilityChanged(value);
}
+QRect KisCloneLayer::needRectOnSourceForMasks(const QRect &rc) const
+{
+ QStack<QRect> applyRects_unused;
+ bool rectVariesFlag;
+
+ QList<KisEffectMaskSP> effectMasks = this->effectMasks();
+ if (effectMasks.isEmpty()) return QRect();
+
+ QRect needRect = this->masksNeedRect(effectMasks,
+ rc,
+ applyRects_unused,
+ rectVariesFlag);
+
+ if (needRect.isEmpty() ||
+ (!rectVariesFlag && needRect == rc)) {
+
+ return QRect();
+ }
+
+ return needRect;
+}
+
qint32 KisCloneLayer::x() const
{
return m_d->x;
@@ -196,8 +221,17 @@ QRect KisCloneLayer::accessRect(const QRect &rect, PositionToFilthy pos) const
{
QRect resultRect = rect;
- if(pos & (N_FILTHY_PROJECTION | N_FILTHY) && (m_d->x || m_d->y)) {
- resultRect |= rect.translated(-m_d->x, -m_d->y);
+ if(pos & (N_FILTHY_PROJECTION | N_FILTHY)) {
+ if (m_d->x || m_d->y) {
+ resultRect |= rect.translated(-m_d->x, -m_d->y);
+ }
+
+ /**
+ * KisUpdateOriginalVisitor will try to recalculate some area
+ * on the clone's source, so this extra rectangle should also
+ * be taken into account
+ */
+ resultRect |= needRectOnSourceForMasks(rect);
}
return resultRect;
diff --git a/krita/image/kis_clone_layer.h b/krita/image/kis_clone_layer.h
index 1de10ff..7f22df2 100644
--- a/krita/image/kis_clone_layer.h
+++ b/krita/image/kis_clone_layer.h
@@ -116,6 +116,8 @@ public:
*/
void setDirtyOriginal(const QRect &rect);
+ QRect needRectOnSourceForMasks(const QRect &rc) const;
+
protected:
void notifyParentVisibilityChanged(bool value);
diff --git a/krita/image/kis_perspectivetransform_worker.cpp b/krita/image/kis_perspectivetransform_worker.cpp
index 6db2ecc..8d45eb9 100644
--- a/krita/image/kis_perspectivetransform_worker.cpp
+++ b/krita/image/kis_perspectivetransform_worker.cpp
@@ -156,7 +156,7 @@ void KisPerspectiveTransformWorker::runPartialDst(KisPaintDeviceSP srcDev,
return;
}
- QRectF srcClipRect = srcDev->defaultBounds()->bounds();
+ QRectF srcClipRect = srcDev->exactBounds();
KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, dstRect.height());
diff --git a/krita/image/kis_safe_transform.cpp b/krita/image/kis_safe_transform.cpp
new file mode 100644
index 0000000..490bdb6
--- /dev/null
+++ b/krita/image/kis_safe_transform.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2014 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_safe_transform.h"
+
+#include <QTransform>
+#include <QLineF>
+#include <QPolygonF>
+
+
+#include "kis_debug.h"
+#include "kis_algebra_2d.h"
+
+
+
+struct KisSafeTransform::Private
+{
+ QRect bounds;
+ QTransform forwardTransform;
+ QTransform backwardTransform;
+
+ QPolygonF srcClipPolygon;
+ QPolygonF dstClipPolygon;
+
+ bool getHorizon(const QTransform &t, QLineF *horizon) {
+ static const qreal eps = 1e-10;
+
+ QPointF vanishingX(t.m11() / t.m13(), t.m12() / t.m13());
+ QPointF vanishingY(t.m21() / t.m23(), t.m22() / t.m23());
+
+ if (qAbs(t.m13()) < eps && qAbs(t.m23()) < eps) {
+ *horizon = QLineF();
+ return false;
+ } else if (qAbs(t.m23()) < eps) {
+ QPointF diff = t.map(QPointF(0.0, 10.0)) - t.map(QPointF());
+ vanishingY = vanishingX + diff;
+ } else if (qAbs(t.m13()) < eps) {
+ QPointF diff = t.map(QPointF(10.0, 0.0)) - t.map(QPointF());
+ vanishingX = vanishingY + diff;
+ }
+
+ *horizon = QLineF(vanishingX, vanishingY);
+ return true;
+ }
+
+ qreal getCrossSign(const QLineF &horizon, const QRectF &rc) {
+ if (rc.isEmpty()) return 1.0;
+
+ QPointF diff = horizon.p2() - horizon.p1();
+ return KisAlgebra2D::signPZ(KisAlgebra2D::crossProduct(diff, rc.center() - horizon.p1()));
+ }
+
+ QPolygonF getCroppedPolygon(const QLineF &baseHorizon, const QRect &rc, const qreal crossCoeff) {
+ if (rc.isEmpty()) return QPolygonF();
+
+ QRectF boundsRect(rc);
+ QPolygonF polygon(boundsRect);
+ QPolygonF result;
+
+ // calculate new (offset) horizon to avoid infinity
+ const qreal offsetLength = 10.0;
+ const QPointF horizonOffset = offsetLength * crossCoeff *
+ KisAlgebra2D::rightUnitNormal(baseHorizon.p2() - baseHorizon.p1());
+
+ const QLineF horizon = baseHorizon.translated(horizonOffset);
+
+ // base vectors to calculate the side of the horizon
+ const QPointF &basePoint = horizon.p1();
+ const QPointF horizonVec = horizon.p2() - basePoint;
+
+
+ // iteration
+ QPointF prevPoint = polygon[polygon.size() - 1];
+ qreal prevCross = crossCoeff * KisAlgebra2D::crossProduct(horizonVec, prevPoint - basePoint);
+
+ for (int i = 0; i < polygon.size(); i++) {
+ const QPointF &pt = polygon[i];
+
+ qreal cross = crossCoeff * KisAlgebra2D::crossProduct(horizonVec, pt - basePoint);
+
+ if ((cross >= 0 && prevCross >= 0) || (cross == 0 && prevCross < 0)) {
+ result << pt;
+ } else if (cross * prevCross < 0) {
+ QPointF intersection;
+ QLineF edge(prevPoint, pt);
+ QLineF::IntersectType intersectionType =
+ horizon.intersect(edge, &intersection);
+
+ KIS_ASSERT_RECOVER_NOOP(intersectionType != QLineF::NoIntersection);
+
+ result << intersection;
+
+ if (cross > 0) {
+ result << pt;
+ }
+ }
+
+ prevPoint = pt;
+ prevCross = cross;
+ }
+
+ if (!result.isClosed()) {
+ result << result.first();
+ }
+
+ return result;
+ }
+
+};
+
+KisSafeTransform::KisSafeTransform(const QTransform &transform,
+ const QRect &bounds,
+ const QRect &srcInterestRect)
+ : m_d(new Private)
+{
+ m_d->bounds = bounds;
+
+ m_d->forwardTransform = transform;
+ m_d->backwardTransform = transform.inverted();
+
+ m_d->srcClipPolygon = QPolygonF(QRectF(m_d->bounds));
+ m_d->dstClipPolygon = QPolygonF(QRectF(m_d->bounds));
+
+ qreal crossCoeff = 1.0;
+
+ QLineF srcHorizon;
+ if (m_d->getHorizon(m_d->backwardTransform, &srcHorizon)) {
+ crossCoeff = m_d->getCrossSign(srcHorizon, srcInterestRect);
+ m_d->srcClipPolygon = m_d->getCroppedPolygon(srcHorizon, m_d->bounds, crossCoeff);
+ }
+
+ QLineF dstHorizon;
+ if (m_d->getHorizon(m_d->forwardTransform, &dstHorizon)) {
+ crossCoeff = m_d->getCrossSign(dstHorizon, mapRectForward(srcInterestRect));
+ m_d->dstClipPolygon = m_d->getCroppedPolygon(dstHorizon, m_d->bounds, crossCoeff);
+ }
+
+}
+
+KisSafeTransform::~KisSafeTransform()
+{
+}
+
+QPolygonF KisSafeTransform::srcClipPolygon() const
+{
+ return m_d->srcClipPolygon;
+}
+
+QPolygonF KisSafeTransform::dstClipPolygon() const
+{
+ return m_d->dstClipPolygon;
+}
+
+QPolygonF KisSafeTransform::mapForward(const QPolygonF &p)
+{
+ QPolygonF poly = m_d->srcClipPolygon.intersected(p);
+ return m_d->forwardTransform.map(poly).intersected(QRectF(m_d->bounds));
+}
+
+QPolygonF KisSafeTransform::mapBackward(const QPolygonF &p)
+{
+ QPolygonF poly = m_d->dstClipPolygon.intersected(p);
+ return m_d->backwardTransform.map(poly).intersected(QRectF(m_d->bounds));
+}
+
+QRectF KisSafeTransform::mapRectForward(const QRectF &rc)
+{
+ return mapForward(rc).boundingRect();
+}
+
+QRectF KisSafeTransform::mapRectBackward(const QRectF &rc)
+{
+ return mapBackward(rc).boundingRect();
+}
+
+QRect KisSafeTransform::mapRectForward(const QRect &rc)
+{
+ return mapRectForward(QRectF(rc)).toAlignedRect();
+}
+
+QRect KisSafeTransform::mapRectBackward(const QRect &rc)
+{
+ return mapRectBackward(QRectF(rc)).toAlignedRect();
+}
+
diff --git a/krita/image/kis_safe_transform.h b/krita/image/kis_safe_transform.h
new file mode 100644
index 0000000..b958c67
--- /dev/null
+++ b/krita/image/kis_safe_transform.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2014 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_SAFE_TRANSFORM_H
+#define __KIS_SAFE_TRANSFORM_H
+
+#include <QScopedPointer>
+
+#include "krita_export.h"
+
+class QTransform;
+class QRect;
+class QRectF;
+class QPolygonF;
+
+
+class KRITAIMAGE_EXPORT KisSafeTransform
+{
+public:
+ KisSafeTransform(const QTransform &transform,
+ const QRect &bounds,
+ const QRect &srcInterestRect);
+
+ ~KisSafeTransform();
+
+ QPolygonF srcClipPolygon() const;
+ QPolygonF dstClipPolygon() const;
+
+ QPolygonF mapForward(const QPolygonF &p);
+ QPolygonF mapBackward(const QPolygonF &p);
+
+ QRectF mapRectForward(const QRectF &rc);
+ QRectF mapRectBackward(const QRectF &rc);
+
+ QRect mapRectForward(const QRect &rc);
+ QRect mapRectBackward(const QRect &rc);
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif /* __KIS_SAFE_TRANSFORM_H */
diff --git a/krita/image/kis_transform_mask.cpp b/krita/image/kis_transform_mask.cpp
index 0b40569..35a4cd7 100644
--- a/krita/image/kis_transform_mask.cpp
+++ b/krita/image/kis_transform_mask.cpp
@@ -39,6 +39,10 @@
#include "kis_transform_mask_params_interface.h"
#include "kis_recalculate_transform_mask_job.h"
#include "kis_signal_compressor.h"
+#include "kis_algebra_2d.h"
+#include "kis_safe_transform.h"
+
+
#define UPDATE_DELAY 3000 /*ms */
@@ -165,7 +169,7 @@ void KisTransformMask::recaclulateStaticImage()
* into account all the change rects of all the masks. Usually,
* this work is done by the walkers.
*/
- QRect requestedRect = parentLayer->changeRect(parentLayer->exactBounds());
+ QRect requestedRect = parentLayer->changeRect(parentLayer->original()->exactBounds());
parentLayer->updateProjection(requestedRect, N_FILTHY_PROJECTION);
m_d->recalculatingStaticImage = false;
@@ -215,14 +219,6 @@ void KisTransformMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *und
return visitor.visit(this, undoAdapter);
}
-QRect calculateLimitingRect(const QRect &bounds, qreal coeff)
-{
- int w = bounds.width() * coeff;
- int h = bounds.height() * coeff;
-
- return bounds.adjusted(-w, -h, w, h);
-}
-
QRect KisTransformMask::changeRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
@@ -234,33 +230,26 @@ QRect KisTransformMask::changeRect(const QRect &rect, PositionToFilthy pos) cons
if (rect.isEmpty()) return rect;
if (!m_d->params->isAffine()) return rect;
- QRect changeRect = m_d->worker.forwardTransform()
- .mapRect(QRectF(rect)).toAlignedRect();
-
- KisNodeSP parentNode;
- KisPaintDeviceSP parentOriginal;
-
- if ((parentNode = parent()) &&
- (parentOriginal = parentNode->original())) {
-
- const QRect bounds = parentOriginal->defaultBounds()->bounds();
- const QRect limitingRect = calculateLimitingRect(bounds, 2);
+ QRect bounds;
+ QRect interestRect;
+ KisNodeSP parentNode = parent();
- changeRect &= limitingRect;
- QRect backwardRect = limitingRect & m_d->worker.backwardTransform().mapRect(rect);
-
- QRegion backwardRegion(backwardRect);
- backwardRegion -= bounds;
- backwardRegion = m_d->worker.forwardTransform().map(backwardRegion);
-
- // FIXME: d-oh... please fix me and use region instead :(
- changeRect |= backwardRegion.boundingRect();
+ if (parentNode) {
+ bounds = parentNode->original()->defaultBounds()->bounds();
+ interestRect = parentNode->original()->extent();
} else {
- qWarning() << "WARNING: a transform mask has no parent, don't know how to limit it";
- const QRect limitingRect(-1000, -1000, 10000, 10000);
- changeRect &= limitingRect;
+ bounds = QRect(0,0,777,777);
+ interestRect = QRect(0,0,888,888);
+ qWarning() << "WARNING: transform mask has no parent (change rect)."
+ << "Cannot run safe transformations."
+ << "Will limit bounds to" << ppVar(bounds);
}
+ const QRect limitingRect = KisAlgebra2D::blowRect(bounds, 0.5);
+
+ KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
+ QRect changeRect = transform.mapRectForward(rect);
+
return changeRect;
}
@@ -275,17 +264,26 @@ QRect KisTransformMask::needRect(const QRect& rect, PositionToFilthy pos) const
if (rect.isEmpty()) return rect;
if (!m_d->params->isAffine()) return rect;
- QRect needRect = kisGrowRect(m_d->worker.backwardTransform().mapRect(rect), 2);
+ QRect bounds;
+ QRect interestRect;
+ KisNodeSP parentNode = parent();
- KisNodeSP parentNode;
-
- if ((parentNode = parent())) {
- needRect &= parentNode->extent();
- } else if (needRect.width() > 1e6 || needRect.height() > 1e6) {
- qWarning() << "WARNING: transform mask returns infinite need rect! Dropping..." << needRect;
- needRect = rect;
+ if (parentNode) {
+ bounds = parentNode->original()->defaultBounds()->bounds();
+ interestRect = parentNode->original()->extent();
+ } else {
+ bounds = QRect(0,0,777,777);
+ interestRect = QRect(0,0,888,888);
+ qWarning() << "WARNING: transform mask has no parent (need rect)."
+ << "Cannot run safe transformations."
+ << "Will limit bounds to" << ppVar(bounds);
}
+ const QRect limitingRect = KisAlgebra2D::blowRect(bounds, 0.5);
+
+ KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
+ QRect needRect = transform.mapRectBackward(rect);
+
return needRect;
}
diff --git a/krita/image/kis_transform_mask_params_interface.cpp b/krita/image/kis_transform_mask_params_interface.cpp
index a9c7e67..becc5cc 100644
--- a/krita/image/kis_transform_mask_params_interface.cpp
+++ b/krita/image/kis_transform_mask_params_interface.cpp
@@ -117,6 +117,11 @@ QTransform KisDumbTransformMaskParams::testingGetTransform() const
return m_d->transform;
}
+void KisDumbTransformMaskParams::testingSetTransform(const QTransform &t)
+{
+ m_d->transform = t;
+}
+
#include "kis_transform_mask_params_factory_registry.h"
struct DumbParamsRegistrar {
diff --git a/krita/image/kis_transform_mask_params_interface.h b/krita/image/kis_transform_mask_params_interface.h
index 7300c01..f9ac651 100644
--- a/krita/image/kis_transform_mask_params_interface.h
+++ b/krita/image/kis_transform_mask_params_interface.h
@@ -64,6 +64,7 @@ public:
// for tesing purposes only
QTransform testingGetTransform() const;
+ void testingSetTransform(const QTransform &t);
private:
struct Private;
diff --git a/krita/image/tests/kis_transform_mask_test.cpp b/krita/image/tests/kis_transform_mask_test.cpp
index b1719d2..1e195f0 100644
--- a/krita/image/tests/kis_transform_mask_test.cpp
+++ b/krita/image/tests/kis_transform_mask_test.cpp
@@ -19,41 +19,405 @@
#include "kis_transform_mask_test.h"
#include <qtest_kde.h>
+
+#include <KoColor.h>
+
+
#include "kis_transform_mask.h"
#include "kis_transform_mask_params_interface.h"
#include "testutil.h"
+#include "kis_algebra_2d.h"
+#include "kis_safe_transform.h"
+#include "kis_clone_layer.h"
+
+
+
+inline QString toOctaveFormat(const QTransform &t)
+{
+ QString s("T = [%1 %2 %3; %4 %5 %6; %7 %8 %9]");
+ s = s
+ .arg(t.m11()).arg(t.m12()).arg(t.m13())
+ .arg(t.m21()).arg(t.m22()).arg(t.m23())
+ .arg(t.m31()).arg(t.m32()).arg(t.m33());
+
+ return s;
+}
+
+void KisTransformMaskTest::testSafeTransform()
+{
+ QTransform transform(-0.177454, -0.805953, -0.00213713,
+ -1.9295, -0.371835, -0.00290463,
+ 3075.05, 2252.32, 7.62371);
+
+ QRectF testRect(0, 1024, 512, 512);
+ KisSafeTransform t2(transform, QRect(0, 0, 2048, 2048), testRect.toRect());
+
+ QPolygonF fwdPoly = t2.mapForward(testRect);
+ QRectF fwdRect = t2.mapRectForward(testRect);
+
+ QPolygonF bwdPoly = t2.mapBackward(fwdPoly);
+ QRectF bwdRect = t2.mapRectBackward(fwdRect);
+
+ QPolygon ref;
+
+ ref.clear();
+ ref << QPoint(284, 410);
+ ref << QPoint(10, 613);
+ ref << QPoint(35, 532);
+ ref << QPoint(236, 403);
+ ref << QPoint(284, 410);
+ QCOMPARE(fwdPoly.toPolygon(), ref);
+ QCOMPARE(fwdRect.toRect(), QRect(10,403,274,211));
+
+ ref.clear();
+ ref << QPoint(512, 1024);
+ ref << QPoint(512, 1536);
+ ref << QPoint(0, 1536);
+ ref << QPoint(0, 1024);
+ ref << QPoint(512, 1024);
+ QCOMPARE(bwdPoly.toPolygon(), ref);
+ QCOMPARE(bwdRect.toRect(), QRect(0, 994, 1198, 584));
+
+/*
+ QImage image(2500, 2500, QImage::Format_ARGB32);
+ QPainter gc(&image);
+ gc.setPen(Qt::cyan);
+
+ gc.setOpacity(0.7);
+
+ gc.setBrush(Qt::red);
+ gc.drawPolygon(t2.srcClipPolygon());
+
+ gc.setBrush(Qt::green);
+ gc.drawPolygon(t2.dstClipPolygon());
+
+ qDebug() << ppVar(testRect);
+ qDebug() << ppVar(fwdPoly);
+ qDebug() << ppVar(fwdRect);
+ qDebug() << ppVar(bwdPoly);
+ qDebug() << ppVar(bwdRect);
+
+ gc.setBrush(Qt::yellow);
+ gc.drawPolygon(testRect);
+
+ gc.setBrush(Qt::red);
+ gc.drawPolygon(fwdRect);
+ gc.setBrush(Qt::blue);
+ gc.drawPolygon(fwdPoly);
+
+ gc.setBrush(Qt::magenta);
+ gc.drawPolygon(bwdRect);
+ gc.setBrush(Qt::cyan);
+ gc.drawPolygon(bwdPoly);
+
+ gc.end();
+ image.save("polygons_safety.png");
+*/
+}
+
+void KisTransformMaskTest::testSafeTransformUnity()
+{
+ QTransform transform;
+
+ QRectF testRect(0, 1024, 512, 512);
+ KisSafeTransform t2(transform, QRect(0, 0, 2048, 2048), testRect.toRect());
+
+ QPolygonF fwdPoly = t2.mapForward(testRect);
+ QRectF fwdRect = t2.mapRectForward(testRect);
+
+ QPolygonF bwdPoly = t2.mapBackward(fwdPoly);
+ QRectF bwdRect = t2.mapRectBackward(fwdRect);
+
+ QCOMPARE(testRect, fwdRect);
+ QCOMPARE(testRect, bwdRect);
+ QCOMPARE(fwdPoly, QPolygonF(testRect));
+ QCOMPARE(bwdPoly, QPolygonF(testRect));
+}
+
+void KisTransformMaskTest::testSafeTransformSingleVanishingPoint()
+{
+ // rotation around 0X has a single vanishing point for 0Y axis
+ QTransform transform(1, 0, 0,
+ -0.870208, -0.414416, -0.000955222,
+ 132.386, 1082.91, 1.99439);
+
+ QTransform R; R.rotateRadians(M_PI / 4.0);
+ //transform *= R;
+
+ QRectF testRect(1536, 1024, 512, 512);
+ KisSafeTransform t2(transform, QRect(0, 0, 2048, 2048), testRect.toRect());
+
+ QPolygonF fwdPoly = t2.mapForward(testRect);
+ QRectF fwdRect = t2.mapRectForward(testRect);
+
+ QPolygonF bwdPoly = t2.mapBackward(fwdPoly);
+ QRectF bwdRect = t2.mapRectBackward(fwdRect);
+
+ /**
+ * A special weird rect that crosses the vanishing point,
+ * which is (911.001, 433.84) in this case
+ */
+ QRectF fwdNastyRect(800, 100, 400, 600);
+ //QRectF fwdNastyRect(100, 400, 1000, 800);
+ QRectF bwdNastyRect = t2.mapRectBackward(fwdNastyRect);
+
+/*
+ qDebug() << ppVar(testRect);
+ qDebug() << ppVar(fwdPoly);
+ qDebug() << ppVar(fwdRect);
+ qDebug() << ppVar(bwdPoly);
+ qDebug() << ppVar(bwdRect);
+ qDebug() << ppVar(bwdNastyRect);
+*/
+
+ QPolygon ref;
+
+ ref.clear();
+ ref << QPoint(765,648);
+ ref << QPoint(1269, 648);
+ ref << QPoint(1601, 847);
+ ref << QPoint(629, 847);
+ ref << QPoint(765, 648);
+ QCOMPARE(fwdPoly.toPolygon(), ref);
+ QCOMPARE(fwdRect.toRect(), QRect(629,648,971,199));
+
+ ref.clear();
+ ref << QPoint(1536,1024);
+ ref << QPoint(2048,1024);
+ ref << QPoint(2048,1536);
+ ref << QPoint(1536,1536);
+ ref << QPoint(1536,1024);
+ QCOMPARE(bwdPoly.toPolygon(), ref);
+ QCOMPARE(bwdRect.toRect(), QRect(1398,1024,650,512));
+
+ QCOMPARE(bwdNastyRect.toRect(), QRect(1463,0,585,1232));
+}
+
+bool checkImage(KisImageSP image, const QString &testName, const QString &prefix) {
+ return TestUtil::checkQImageExternal(image->projection()->convertToQImage(0, image->bounds()),
+ "transform_mask_updates",
+ prefix,
+ testName, 1, 1, 100);
+}
+
+bool doPartialTests(const QString &prefix, KisImageSP image, KisLayerSP paintLayer,
+ KisLayerSP visibilityToggleLayer, KisTransformMaskSP mask)
+{
+ bool result = true;
+
+ QRect refRect = image->bounds();
+
+ int testIndex = 1;
+ QString testName;
+
+ for (int y = 0; y < refRect.height(); y += 512) {
+ for (int x = 0; x < refRect.width(); x += 512) {
+ QRect rc(x, y, 512, 512);
+
+ if (rc.right() > refRect.right()) {
+ rc.setRight(refRect.right());
+ if (rc.isEmpty()) continue;
+ }
+
+ if (rc.bottom() > refRect.bottom()) {
+ rc.setBottom(refRect.bottom());
+ if (rc.isEmpty()) continue;
+ }
+
+ paintLayer->setDirty(rc);
+ image->waitForDone();
+ testName = QString("tm_%1_partial_%2_%3").arg(testIndex++).arg(x).arg(y);
+ result &= checkImage(image, testName, prefix);
+ }
+ }
+
+ // initial update of the mask to clear the unused portions of the projection
+ // (it updates only when we call set dirty on the mask itself, which happens
+ // in Krita right after the addition of the mask onto a layer)
+
+ mask->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_initial_mask_visible_on").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ // start layer visibility testing
+
+ paintLayer->setVisible(false);
+ paintLayer->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_layer_visible_off").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ paintLayer->setVisible(true);
+ paintLayer->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_layer_visible_on").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ if (paintLayer != visibilityToggleLayer) {
+ visibilityToggleLayer->setVisible(false);
+ visibilityToggleLayer->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_extra_layer_visible_off").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+
+ visibilityToggleLayer->setVisible(true);
+ visibilityToggleLayer->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_extra_layer_visible_on").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+ }
+
+ // toggle mask visibility
+
+ mask->setVisible(false);
+ mask->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_mask_visible_off").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ mask->setVisible(true);
+ mask->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_mask_visible_on").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ // entire bounds update
+
+ // no clearing, just don't hang up
+
+ paintLayer->setDirty(refRect);
+ image->waitForDone();
+ testName = QString("tm_%1_layer_dirty_bounds").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ // no clearing, just don't hang up
+
+ mask->setDirty(refRect);
+ image->waitForDone();
+ testName = QString("tm_%1_mask_dirty_bounds").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ if (paintLayer != visibilityToggleLayer) {
+ // no clearing, just don't hang up
+
+ visibilityToggleLayer->setDirty(refRect);
+ image->waitForDone();
+ testName = QString("tm_%1_extra_layer_dirty_bounds").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+ }
+
+ QRect fillRect;
+
+ // partial updates outside
+
+ fillRect = QRect(-100, 0.5 * refRect.height(), 50, 100);
+ paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace()));
+ paintLayer->setDirty(fillRect);
+ image->waitForDone();
+ testName = QString("tm_%1_layer_dirty_outside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y());
+ result &= checkImage(image, testName, prefix);
-void KisTransformMaskTest::test()
+ fillRect = QRect(0.5 * refRect.width(), -100, 100, 50);
+ paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace()));
+ paintLayer->setDirty(fillRect);
+ image->waitForDone();
+ testName = QString("tm_%1_layer_dirty_outside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y());
+ result &= checkImage(image, testName, prefix);
+
+ fillRect = QRect(refRect.width() + 50, 0.2 * refRect.height(), 50, 100);
+ paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace()));
+ paintLayer->setDirty(fillRect);
+ image->waitForDone();
+ testName = QString("tm_%1_layer_dirty_outside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y());
+ result &= checkImage(image, testName, prefix);
+
+ // partial update inside
+
+ fillRect = QRect(0.5 * refRect.width() - 50, 0.5 * refRect.height() - 50, 100, 100);
+ paintLayer->paintDevice()->fill(fillRect, KoColor(Qt::red, image->colorSpace()));
+ paintLayer->setDirty(fillRect);
+ image->waitForDone();
+ testName = QString("tm_%1_layer_dirty_inside_%2_%3").arg(testIndex++).arg(fillRect.x()).arg(fillRect.y());
+ result &= checkImage(image, testName, prefix);
+
+ // clear explicitly
+ image->projection()->clear();
+
+ mask->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_mask_dirty_bounds").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+
+ KisDumbTransformMaskParams *params =
+ dynamic_cast<KisDumbTransformMaskParams*>(mask->transformParams().data());
+
+ QTransform t = params->testingGetTransform();
+ t *= QTransform::fromTranslate(400, 300);
+ params->testingSetTransform(t);
+ mask->setTransformParams(mask->transformParams());
+
+ mask->setDirty();
+ image->waitForDone();
+ testName = QString("tm_%1_mask_dirty_after_offset").arg(testIndex++);
+ result &= checkImage(image, testName, prefix);
+
+ return result;
+}
+
+void KisTransformMaskTest::testMaskOnPaintLayer()
{
QImage refImage(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
- TestUtil::MaskParent p(refImage.rect());
+ QRect refRect = refImage.rect();
+ TestUtil::MaskParent p(refRect);
p.layer->paintDevice()->convertFromQImage(refImage, 0);
- KisTransformMaskSP mask = new KisTransformMask();
+ KisPaintLayerSP player = new KisPaintLayer(p.image, "bg", OPACITY_OPAQUE_U8, p.image->colorSpace());
+ p.image->addNode(player, p.image->root(), KisNodeSP());
+ KisTransformMaskSP mask = new KisTransformMask();
p.image->addNode(mask, p.layer);
- QTransform transform =
- QTransform::fromTranslate(100.0, 0.0) *
- QTransform::fromScale(2.0, 1.0);
+ QTransform transform(-0.177454, -0.805953, -0.00213713,
+ -1.9295, -0.371835, -0.00290463,
+ 3075.05, 2252.32, 7.62371);
mask->setTransformParams(KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams(transform)));
- p.layer->setDirty(QRect(160, 160, 150, 300));
- p.layer->setDirty(QRect(310, 160, 150, 300));
- p.layer->setDirty(QRect(460, 160, 150, 300));
- p.layer->setDirty(QRect(610, 160, 150, 300));
- p.layer->setDirty(QRect(760, 160, 150, 300));
+ QVERIFY(doPartialTests("pl", p.image, p.layer, p.layer, mask));
+}
+
+void KisTransformMaskTest::testMaskOnCloneLayer()
+{
+ QImage refImage(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
+ QRect refRect = refImage.rect();
+ TestUtil::MaskParent p(refRect);
+
+ p.layer->paintDevice()->convertFromQImage(refImage, 0);
- p.image->waitForDone();
+ KisPaintLayerSP player = new KisPaintLayer(p.image, "bg", OPACITY_OPAQUE_U8, p.image->colorSpace());
+ p.image->addNode(player, p.image->root(), KisNodeSP());
- QImage result = p.layer->projection()->convertToQImage(0);
- TestUtil::checkQImage(result, "transform_mask_test", "partial", "single");
+ KisCloneLayerSP clone = new KisCloneLayer(p.layer, p.image, "clone", OPACITY_OPAQUE_U8);
+ p.image->addNode(clone, p.image->root());
+
+ KisTransformMaskSP mask = new KisTransformMask();
+ p.image->addNode(mask, clone);
+
+ QTransform transform(-0.177454, -0.805953, -0.00213713,
+ -1.9295, -0.371835, -0.00290463,
+ 3075.05, 2252.32, 7.62371);
+
+ mask->setTransformParams(KisTransformMaskParamsInterfaceSP(
+ new KisDumbTransformMaskParams(transform)));
+ QVERIFY(doPartialTests("cl", p.image, p.layer, clone, mask));
}
QTEST_KDEMAIN(KisTransformMaskTest, GUI)
diff --git a/krita/image/tests/kis_transform_mask_test.h b/krita/image/tests/kis_transform_mask_test.h
index 32afb01..a534bb8 100644
--- a/krita/image/tests/kis_transform_mask_test.h
+++ b/krita/image/tests/kis_transform_mask_test.h
@@ -25,7 +25,11 @@ class KisTransformMaskTest : public QObject
{
Q_OBJECT
private slots:
- void test();
+ void testSafeTransform();
+ void testMaskOnPaintLayer();
+ void testMaskOnCloneLayer();
+ void testSafeTransformUnity();
+ void testSafeTransformSingleVanishingPoint();
};
#endif /* __KIS_TRANSFORM_MASK_TEST_H */
diff --git a/krita/sdk/tests/testutil.h b/krita/sdk/tests/testutil.h
index 651a18f..2e12b27 100644
--- a/krita/sdk/tests/testutil.h
+++ b/krita/sdk/tests/testutil.h
@@ -63,24 +63,51 @@ inline KisNodeSP findNode(KisNodeSP root, const QString &name) {
return 0;
}
-inline QString fetchDataFileLazy(const QString relativeFileName)
+#include <QProcessEnvironment>
+
+inline QString fetchExternalDataFileName(const QString relativeFileName)
{
+ static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ static QString unittestsDataDirPath = "KRITA_UNITTESTS_DATA_DIR";
+
+ QString path;
+ if (!env.contains(unittestsDataDirPath)) {
+ qWarning() << "Environment variable" << unittestsDataDirPath << "is not set";
+ return QString();
+ } else {
+ path = env.value(unittestsDataDirPath, "");
+ }
+
QString filename =
- QString(FILES_DATA_DIR) +
+ path +
QDir::separator() +
relativeFileName;
- if (QFileInfo(filename).exists()) {
- return filename;
- }
+ return filename;
+}
- filename =
- QString(FILES_DEFAULT_DATA_DIR) +
- QDir::separator() +
- relativeFileName;
+inline QString fetchDataFileLazy(const QString relativeFileName, bool externalTest = false)
+{
+ if (externalTest) {
+ return fetchExternalDataFileName(relativeFileName);
+ } else {
+ QString filename =
+ QString(FILES_DATA_DIR) +
+ QDir::separator() +
+ relativeFileName;
+
+ if (QFileInfo(filename).exists()) {
+ return filename;
+ }
+
+ filename =
+ QString(FILES_DEFAULT_DATA_DIR) +
+ QDir::separator() +
+ relativeFileName;
- if (QFileInfo(filename).exists()) {
- return filename;
+ if (QFileInfo(filename).exists()) {
+ return filename;
+ }
}
return QString();
@@ -135,7 +162,7 @@ private:
};
-inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0)
+inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0)
{
// QTime t;
// t.start();
@@ -153,6 +180,8 @@ inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & im
return false;
}
+ int numFailingPixels = 0;
+
for (int y = 0; y < h1; ++y) {
const QRgb * const firstLine = reinterpret_cast<const QRgb *>(image2.scanLine(y));
const QRgb * const secondLine = reinterpret_cast<const QRgb *>(image1.scanLine(y));
@@ -170,11 +199,19 @@ inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & im
if (!bothTransparent && (!same || !sameAlpha)) {
pt.setX(x);
pt.setY(y);
+ numFailingPixels++;
+
qDebug() << " Different at" << pt
<< "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a)
<< "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b)
- << "fuzzy" << fuzzy;
- return false;
+ << "fuzzy" << fuzzy
+ << "fuzzyAlpha" << fuzzyAlpha
+ << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )";
+
+
+ if (numFailingPixels > maxNumFailingPixels) {
+ return false;
+ }
}
}
}
@@ -257,44 +294,99 @@ inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPain
#ifdef FILES_OUTPUT_DIR
-inline bool checkQImage(const QImage &image, const QString &testName,
- const QString &prefix, const QString &name,
- int fuzzy = 0)
+inline bool checkQImageImpl(bool externalTest,
+ const QImage &image, const QString &testName,
+ const QString &prefix, const QString &name,
+ int fuzzy, int fuzzyAlpha, int maxNumFailingPixels)
{
- Q_UNUSED(fuzzy);
+ if (fuzzyAlpha == -1) {
+ fuzzyAlpha = fuzzy;
+ }
+
+
QString filename(prefix + "_" + name + ".png");
QString dumpName(prefix + "_" + name + "_expected.png");
- QString fullPath = fetchDataFileLazy(testName + QDir::separator() +
- prefix + QDir::separator() + filename);
+ const QString standardPath =
+ testName + QDir::separator() +
+ prefix + QDir::separator() + filename;
- if (fullPath.isEmpty()) {
+ QString fullPath = fetchDataFileLazy(standardPath, externalTest);
+
+ if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
// Try without the testname subdirectory
fullPath = fetchDataFileLazy(prefix + QDir::separator() +
- filename);
+ filename,
+ externalTest);
}
- if (fullPath.isEmpty()) {
+ if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
// Try without the prefix subdirectory
fullPath = fetchDataFileLazy(testName + QDir::separator() +
- filename);
+ filename,
+ externalTest);
}
+ bool canSkipExternalTest = fullPath.isEmpty() && externalTest;
+
QImage ref(fullPath);
bool valid = true;
QPoint t;
- if(!compareQImages(t, image, ref)) {
- qDebug() << "--- Wrong image:" << name;
- valid = false;
+ if(!compareQImages(t, image, ref, fuzzy, fuzzyAlpha, maxNumFailingPixels)) {
+ bool saveStandardResults = true;
+
+ if (canSkipExternalTest) {
+ static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ static QString writeUnittestsVar = "KRITA_WRITE_UNITTESTS";
+
+ int writeUnittests = env.value(writeUnittestsVar, "0").toInt();
+ if (writeUnittests) {
+ QString path = fetchExternalDataFileName(standardPath);
+
+ QFileInfo pathInfo(path);
+ QDir directory;
+ directory.mkpath(pathInfo.path());
+
+ qDebug() << "--- Saving reference image:" << name << path;
+ image.save(path);
+ saveStandardResults = false;
+
+ } else {
+ qDebug() << "--- External image not found. Skipping..." << name;
+ }
+ } else {
+ qDebug() << "--- Wrong image:" << name;
+ valid = false;
+ }
- image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + filename);
- ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + dumpName);
+ if (saveStandardResults) {
+ image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + filename);
+ ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + dumpName);
+ }
}
return valid;
}
+inline bool checkQImage(const QImage &image, const QString &testName,
+ const QString &prefix, const QString &name,
+ int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
+{
+ return checkQImageImpl(false, image, testName,
+ prefix, name,
+ fuzzy, fuzzyAlpha, maxNumFailingPixels);
+}
+
+inline bool checkQImageExternal(const QImage &image, const QString &testName,
+ const QString &prefix, const QString &name,
+ int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
+{
+ return checkQImageImpl(true, image, testName,
+ prefix, name,
+ fuzzy, fuzzyAlpha, maxNumFailingPixels);
+}
+
#endif
inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y)
More information about the kimageshop
mailing list