Commit 57c3194c authored by Agata Cacko's avatar Agata Cacko

Fix results of Nearest Neighbour filter

Summary:
Before this patch the NN filter would create intermediate colors
in some circumstances, especially when scaling with the scale <50%.
Now the filter will choose one of the colors around instead of
blending colors around to get the result color.

BUG:401788

Test Plan:
- added tests to test the behaviour of NN filter
- testing on the image: http://www.bealecorner.org/red/test-patterns/star-chart-bars144-600dpi.png
- testing on the image from bug report

Reviewers: dkazakov, #krita

Reviewed By: dkazakov, #krita

Tags: #krita

Differential Revision: https://phabricator.kde.org/D20246
parent 0cf7c069
......@@ -26,19 +26,22 @@
#include <QGlobalStatic>
#include "kis_debug.h"
#include <QtMath>
Q_GLOBAL_STATIC(KisFilterStrategyRegistry, s_instance)
qreal KisHermiteFilterStrategy::valueAt(qreal t) const
qreal KisHermiteFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
/* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */
if (t < 0.0) t = -t;
if (t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0);
return(0.0);
}
qint32 KisHermiteFilterStrategy::intValueAt(qint32 t) const
qint32 KisHermiteFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
/* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */
if (t < 0) t = -t;
if (t < 256) {
......@@ -55,8 +58,9 @@ qint32 KisHermiteFilterStrategy::intValueAt(qint32 t) const
return(0);
}
qint32 KisBicubicFilterStrategy::intValueAt(qint32 t) const
qint32 KisBicubicFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
/* f(t) = 1.5|t|^3 - 2.5|t|^2 + 1, -1 <= t <= 1 */
if (t < 0) t = -t;
if (t < 256) {
......@@ -85,29 +89,42 @@ qint32 KisBicubicFilterStrategy::intValueAt(qint32 t) const
return(0);
}
qreal KisBoxFilterStrategy::valueAt(qreal t) const
qreal KisBoxFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
{
if ((t > -0.5) && (t <= 0.5)) return(1.0);
if ((t >= -0.5 * weightsPositionScale) && (t < 0.5 * weightsPositionScale)) return(1.0);
return(0.0);
}
qint32 KisBoxFilterStrategy::intValueAt(qint32 t) const
qint32 KisBoxFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
{
/* f(t) = 1, -0.5 < t <= 0.5 */
if ((t > -128) && (t <= 128))
if ((t >= -128 * weightsPositionScale) && (t < 128 * weightsPositionScale))
return 255;
return 0;
}
qreal KisBilinearFilterStrategy::valueAt(qreal t) const
qreal KisBoxFilterStrategy::support(qreal weightsPositionScale)
{
return supportVal*weightsPositionScale;
}
qint32 KisBoxFilterStrategy::intSupport(qreal weightsPositionScale)
{
return qCeil(intSupportVal*weightsPositionScale);
}
qreal KisBilinearFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
if (t < 0.0) t = -t;
if (t < 1.0) return(1.0 - t);
return(0.0);
}
qint32 KisBilinearFilterStrategy::intValueAt(qint32 t) const
qint32 KisBilinearFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
/* f(t) = |t|, -1 <= t <= 1 */
if (t < 0) t = -t;
if (t < 256) {
......@@ -119,8 +136,9 @@ qint32 KisBilinearFilterStrategy::intValueAt(qint32 t) const
}
qreal KisBellFilterStrategy::valueAt(qreal t) const
qreal KisBellFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
if (t < 0) t = -t;
if (t < .5) return(.75 - (t * t));
if (t < 1.5) {
......@@ -130,8 +148,9 @@ qreal KisBellFilterStrategy::valueAt(qreal t) const
return(0.0);
}
qreal KisBSplineFilterStrategy::valueAt(qreal t) const
qreal KisBSplineFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
qreal tt;
if (t < 0) t = -t;
......@@ -145,8 +164,9 @@ qreal KisBSplineFilterStrategy::valueAt(qreal t) const
return(0.0);
}
qreal KisLanczos3FilterStrategy::valueAt(qreal t) const
qreal KisLanczos3FilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
if (t < 0) t = -t;
if (t < 3.0) return(sinc(t) * sinc(t / 3.0));
return(0.0);
......@@ -160,8 +180,9 @@ qreal KisLanczos3FilterStrategy::sinc(qreal x) const
return(1.0);
}
qreal KisMitchellFilterStrategy::valueAt(qreal t) const
qreal KisMitchellFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
{
Q_UNUSED(weightsPositionScale);
const qreal B = 1.0 / 3.0;
const qreal C = 1.0 / 3.0;
qreal tt;
......
......@@ -39,16 +39,20 @@ public:
QString name() {
return m_id.name();
}
virtual qreal valueAt(qreal /*t*/) const {
virtual qreal valueAt(qreal t, qreal weightsPositionScale) const {
Q_UNUSED(t);
Q_UNUSED(weightsPositionScale);
return 0;
}
virtual qint32 intValueAt(qint32 t) const {
return qint32(255*valueAt(t / 256.0));
virtual qint32 intValueAt(qint32 t, qreal weightsPositionScale) const {
return qint32(255*valueAt(t / 256.0, weightsPositionScale));
}
qreal support() {
virtual qreal support(qreal weightsPositionScale) {
Q_UNUSED(weightsPositionScale);
return supportVal;
}
qint32 intSupport() {
virtual qint32 intSupport(qreal weightsPositionScale) {
Q_UNUSED(weightsPositionScale);
return intSupportVal;
}
virtual QString description() {
......@@ -69,8 +73,8 @@ public:
}
~KisHermiteFilterStrategy() override {}
qint32 intValueAt(qint32 t) const override;
qreal valueAt(qreal t) const override;
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBicubicFilterStrategy : public KisFilterStrategy
......@@ -85,7 +89,7 @@ public:
return i18n("Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.");
}
qint32 intValueAt(qint32 t) const override;
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBoxFilterStrategy : public KisFilterStrategy
{
......@@ -99,8 +103,12 @@ public:
return i18n("Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.");
}
qint32 intValueAt(qint32 t) const override;
qreal valueAt(qreal t) const override;
virtual qreal support(qreal weightsPositionScale) override;
virtual qint32 intSupport(qreal weightsPositionScale) override;
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBilinearFilterStrategy : public KisFilterStrategy
......@@ -115,8 +123,8 @@ public:
return i18n("Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.");
}
qint32 intValueAt(qint32 t) const override;
qreal valueAt(qreal t) const override;
qint32 intValueAt(qint32 t, qreal weightsPositionScale) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBellFilterStrategy : public KisFilterStrategy
......@@ -127,7 +135,7 @@ public:
}
~KisBellFilterStrategy() override {}
qreal valueAt(qreal t) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisBSplineFilterStrategy : public KisFilterStrategy
......@@ -138,7 +146,7 @@ public:
}
~KisBSplineFilterStrategy() override {}
qreal valueAt(qreal t) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisLanczos3FilterStrategy : public KisFilterStrategy
......@@ -153,7 +161,7 @@ public:
return i18n("Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.");
}
qreal valueAt(qreal t) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
private:
qreal sinc(qreal x) const;
};
......@@ -166,7 +174,7 @@ public:
}
~KisMitchellFilterStrategy() override {}
qreal valueAt(qreal t) const override;
qreal valueAt(qreal t, qreal weightsPositionScale) const override;
};
class KRITAIMAGE_EXPORT KisFilterStrategyRegistry : public KoGenericRegistry<KisFilterStrategy *>
......
......@@ -131,6 +131,7 @@ public:
KisFixedPoint dst_c = l_to_c(dst_l);
KisFixedPoint dst_c_in_src = dstToSrc(dst_c.toFloat(), line);
// gives the nearest center of the pixel in src ( x e (0, 1> => f(x) = 0.5, x e (1, 2> => f(x) = 1.5 etc. )
KisFixedPoint next_c_in_src = (dst_c_in_src - qreal(0.5)).toIntCeil() + qreal(0.5);
BlendSpan span;
......@@ -219,8 +220,12 @@ public:
if (dstStart >= dstEnd) return LinePos(dstStart, 0);
if (leftSrcBorder >= rightSrcBorder) return LinePos(dstStart, 0);
if (leftSrcBorder > srcLine.start()) return LinePos(dstStart, 0);
if (srcLine.end() > rightSrcBorder) return LinePos(dstStart, 9);
if (leftSrcBorder > srcLine.start()) {
leftSrcBorder = srcLine.start();
}
if (srcLine.end() > rightSrcBorder) {
rightSrcBorder = srcLine.end();
}
int pixelSize = m_src->pixelSize();
KoMixColorsOp *mixOp = m_src->colorSpace()->mixColorsOp();
......
......@@ -167,12 +167,13 @@ public:
KisFixedPoint supportDst;
if (realScale < 1.0) {
supportSrc.from256Frac(filterStrategy->intSupport() / realScale);
supportDst.from256Frac(filterStrategy->intSupport());
m_weightsPositionScale = KisFixedPoint(realScale);
supportSrc.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()) / realScale);
supportDst.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()));
} else {
supportSrc.from256Frac(filterStrategy->intSupport());
supportDst.from256Frac(filterStrategy->intSupport());
supportSrc.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()));
supportDst.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()));
}
for (int i = 0; i < 256; i++) {
......@@ -203,7 +204,7 @@ public:
int sum = 0;
for (int j = 0; j < span; j++) {
int t = filterStrategy->intValueAt(scaledIter.to256Frac());
int t = filterStrategy->intValueAt(scaledIter.to256Frac(), m_weightsPositionScale.toFloat());
m_filterWeights[i].weight[j] = t;
sum += t;
......
......@@ -216,7 +216,7 @@ void KisTransformWorker::transformPass(KisPaintDevice *src, KisPaintDevice *dst,
KisFilterWeightsApplicator::LinePos dstPos;
KisFilterWeightsApplicator::LinePos srcPos(srcStart, srcLen);
dstPos = applicator.processLine<T>(srcPos, i, &buf, filterStrategy->support());
dstPos = applicator.processLine<T>(srcPos, i, &buf, filterStrategy->support(buf.weightsPositionScale().toFloat()));
dstBounds.unite(dstPos);
progressHelper.step();
......
......@@ -198,34 +198,54 @@ void checkRA(KisPaintDeviceSP dev, int x0, int len, quint8 r[], quint8 a[], bool
}
}
void testLineImpl(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge, bool horizontal)
void testLineImpl(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge, bool horizontal, KisFilterStrategy *filter = 0, KisPaintDeviceSP dev = 0)
{
int startPos = 0;
int endPos = 4;
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
KisFilterStrategy *filter = new KisBilinearFilterStrategy();
if (!filter) {
filter = new KisBilinearFilterStrategy();
}
if (!dev) {
dev = new KisPaintDevice(cs);
KisFilterWeightsBuffer buf(filter, qAbs(scale));
KisFilterWeightsApplicator applicator(dev, dev, scale, 0.0, dx, clampToEdge);
for (int i = 0; i < 4; i++) {
int x = horizontal ? i : 0;
int y = horizontal ? 0 : i;
dev->setPixel(x,y,QColor(10 + i * 10, 20 + i * 10, 40 + i * 10));
}
for (int i = 0; i < 4; i++) {
int x = horizontal ? i : 0;
int y = horizontal ? 0 : i;
dev->setPixel(x,y,QColor(10 + i * 10, 20 + i * 10, 40 + i * 10));
}
{
quint8 r[] = { 0, 10, 20, 30, 40, 0, 0};
quint8 a[] = { 0,255,255,255,255, 0, 0};
checkRA(dev, -1, 6, r, a, horizontal);
}
startPos = 0;
endPos = 4;
{
quint8 r[] = { 0, 10, 20, 30, 40, 0, 0};
quint8 a[] = { 0,255,255,255,255, 0, 0};
checkRA(dev, -1, 6, r, a, horizontal);
} else {
QRect rc = dev->exactBounds();
if (horizontal) {
startPos = rc.left();
endPos = rc.left() + rc.width();
} else {
startPos = rc.top();
endPos = rc.top() + rc.height();
}
}
KisFilterWeightsApplicator::LinePos srcPos(0,4);
KisFilterWeightsBuffer buf(filter, qAbs(scale));
KisFilterWeightsApplicator applicator(dev, dev, scale, 0.0, dx, clampToEdge);
KisFilterWeightsApplicator::LinePos srcPos(startPos, endPos);
KisFilterWeightsApplicator::LinePos dstPos;
if (horizontal) {
dstPos = applicator.processLine<KisHLineIteratorSP>(srcPos,0,&buf, filter->support());
dstPos = applicator.processLine<KisHLineIteratorSP>(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat()));
} else {
dstPos = applicator.processLine<KisVLineIteratorSP>(srcPos,0,&buf, filter->support());
dstPos = applicator.processLine<KisVLineIteratorSP>(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat()));
}
QRect rc = dev->exactBounds();
......@@ -242,10 +262,10 @@ void testLineImpl(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, i
checkRA(dev, x0, len, expR, expA, horizontal);
}
void testLine(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge = false)
void testLine(qreal scale, qreal dx, quint8 expR[], quint8 expA[], int x0, int len, bool clampToEdge = false, KisFilterStrategy* filter = 0, KisPaintDeviceSP dev = 0)
{
testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, true);
testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, false);
testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, true, filter, dev);
testLineImpl(scale, dx, expR, expA, x0, len, clampToEdge, false, filter, dev);
}
void KisFilterWeightsApplicatorTest::testProcessLine_Scale_1_0_Aligned()
......@@ -435,6 +455,173 @@ void KisFilterWeightsApplicatorTest::testProcessLine_Scale_0_5_Shift_0_125_Mirro
testLine(scale, dx, r, a, -6, 10);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_2x()
{
qreal scale = 2.0;
qreal dx = 0;
quint8 r[] = {0, 10, 10, 20, 20, 30, 30, 40, 40, 0, 0};
quint8 a[] = {0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 11, true, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_1x()
{
qreal scale = 1.0;
qreal dx = 0;
quint8 r[] = { 0, 10, 20, 30, 40, 0, 0};
quint8 a[] = { 0,255,255,255,255, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_05x()
{
qreal scale = 0.5;
qreal dx = 0;
quint8 r[] = { 0, 10, 30, 0, 0, 0, 0};
quint8 a[] = { 0,255,255, 0, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_077x()
{
qreal scale = 0.77;
qreal dx = 0;
quint8 r[] = { 0, 10, 20, 40, 0, 0, 0};
quint8 a[] = { 0,255,255, 255, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_074x()
{
qreal scale = 0.74;
qreal dx = 0;
quint8 r[] = { 0, 10, 30, 40, 0, 0, 0};
quint8 a[] = { 0,255,255, 255, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_075x()
{
qreal scale = 0.75;
qreal dx = 0;
quint8 r[] = { 0, 10, 20, 40, 0, 0, 0};
quint8 a[] = { 0,255,255, 255, 0, 0, 0};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_15x()
{
qreal scale = 1.5;
qreal dx = 0;
quint8 r[] = { 0, 10, 10, 20, 30, 30, 40};
quint8 a[] = { 0,255,255, 255, 255, 255, 255};
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLine(scale, dx, r, a, -1, 7, false, filter);
}
KisPaintDeviceSP prepareUniformPaintDevice(int pixelsNumber, bool horizontal)
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
for (int i = 0; i < pixelsNumber; i++) {
int x = horizontal ? i : 0;
int y = horizontal ? 0 : i;
QColor c = QColor(10, 0, 0, 255);
dev->setPixel(x, y, c);
}
return dev;
}
void prepareUniformPixels(quint8 r[], quint8 a[], int pixelsNumber, bool horizontal)
{
for (int i = 0; i < pixelsNumber; i++) {
QColor c = QColor(10, 0, 0, 255);
r[i] = c.red();
a[i] = c.alpha();
}
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_0098x_horizontal()
{
int before = 5075;
int after = 500;
qreal scale = before/after;
qreal dx = 0;
bool horizontal = true;
KisPaintDeviceSP dev = prepareUniformPaintDevice(before, horizontal);
quint8 *r = new quint8[after];
quint8 *a = new quint8[after];
prepareUniformPixels(r, a, after, horizontal);
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLineImpl(scale, dx, r, a, 0, after, false, horizontal, filter, dev);
}
void KisFilterWeightsApplicatorTest::testProcessLine_NearestNeighbourFilter_0098x_vertical()
{
int before = 4725;
int after = 466;
qreal scale = before/after;
qreal dx = 0;
bool horizontal = false;
KisPaintDeviceSP dev = prepareUniformPaintDevice(before, horizontal);
quint8 *r = new quint8[after];
quint8 *a = new quint8[after];
prepareUniformPixels(r, a, after, horizontal);
KisFilterStrategy* filter = new KisBoxFilterStrategy();
testLineImpl(scale, dx, r, a, 0, after, false, horizontal, filter, dev);
}
void KisFilterWeightsApplicatorTest::benchmarkProcesssLine()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
......@@ -454,7 +641,7 @@ void KisFilterWeightsApplicatorTest::benchmarkProcesssLine()
KisFilterWeightsApplicator::LinePos linePos(0,32767);
QBENCHMARK {
applicator.processLine<KisHLineIteratorSP>(linePos,0,&buf, filter->support());
applicator.processLine<KisHLineIteratorSP>(linePos,0,&buf, filter->support(buf.weightsPositionScale().toFloat()));
}
}
......
......@@ -64,6 +64,16 @@ private Q_SLOTS:
void testProcessLine_Scale_0_5_Aligned_Mirrored_Clamped();
void testProcessLine_Scale_0_5_Shift_0_125_Mirrored();
void testProcessLine_NearestNeighbourFilter_2x();
void testProcessLine_NearestNeighbourFilter_1x();
void testProcessLine_NearestNeighbourFilter_05x();
void testProcessLine_NearestNeighbourFilter_077x();
void testProcessLine_NearestNeighbourFilter_074x();
void testProcessLine_NearestNeighbourFilter_075x();
void testProcessLine_NearestNeighbourFilter_15x();
void testProcessLine_NearestNeighbourFilter_0098x_horizontal();
void testProcessLine_NearestNeighbourFilter_0098x_vertical();
void benchmarkProcesssLine();
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment