diff --git a/libs/image/kis_filter_strategy.h b/libs/image/kis_filter_strategy.h --- a/libs/image/kis_filter_strategy.h +++ b/libs/image/kis_filter_strategy.h @@ -39,16 +39,20 @@ 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 @@ } ~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 @@ 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 @@ 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 @@ 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 @@ } ~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 @@ } ~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 @@ 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 @@ } ~KisMitchellFilterStrategy() override {} - qreal valueAt(qreal t) const override; + qreal valueAt(qreal t, qreal weightsPositionScale) const override; }; class KRITAIMAGE_EXPORT KisFilterStrategyRegistry : public KoGenericRegistry diff --git a/libs/image/kis_filter_strategy.cc b/libs/image/kis_filter_strategy.cc --- a/libs/image/kis_filter_strategy.cc +++ b/libs/image/kis_filter_strategy.cc @@ -26,19 +26,22 @@ #include #include "kis_debug.h" +#include 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 @@ 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 @@ 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 @@ } -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 @@ 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 @@ 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 @@ 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; diff --git a/libs/image/kis_filter_weights_applicator.h b/libs/image/kis_filter_weights_applicator.h --- a/libs/image/kis_filter_weights_applicator.h +++ b/libs/image/kis_filter_weights_applicator.h @@ -131,6 +131,7 @@ 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 @@ 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(); diff --git a/libs/image/kis_filter_weights_buffer.h b/libs/image/kis_filter_weights_buffer.h --- a/libs/image/kis_filter_weights_buffer.h +++ b/libs/image/kis_filter_weights_buffer.h @@ -167,12 +167,13 @@ 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 @@ 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; diff --git a/libs/image/kis_transform_worker.cc b/libs/image/kis_transform_worker.cc --- a/libs/image/kis_transform_worker.cc +++ b/libs/image/kis_transform_worker.cc @@ -216,7 +216,7 @@ KisFilterWeightsApplicator::LinePos dstPos; KisFilterWeightsApplicator::LinePos srcPos(srcStart, srcLen); - dstPos = applicator.processLine(srcPos, i, &buf, filterStrategy->support()); + dstPos = applicator.processLine(srcPos, i, &buf, filterStrategy->support(buf.weightsPositionScale().toFloat())); dstBounds.unite(dstPos); progressHelper.step(); diff --git a/libs/image/tests/kis_filter_weights_applicator_test.h b/libs/image/tests/kis_filter_weights_applicator_test.h --- a/libs/image/tests/kis_filter_weights_applicator_test.h +++ b/libs/image/tests/kis_filter_weights_applicator_test.h @@ -64,6 +64,16 @@ 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(); }; diff --git a/libs/image/tests/kis_filter_weights_applicator_test.cpp b/libs/image/tests/kis_filter_weights_applicator_test.cpp --- a/libs/image/tests/kis_filter_weights_applicator_test.cpp +++ b/libs/image/tests/kis_filter_weights_applicator_test.cpp @@ -198,34 +198,54 @@ } } -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(srcPos,0,&buf, filter->support()); + dstPos = applicator.processLine(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat())); } else { - dstPos = applicator.processLine(srcPos,0,&buf, filter->support()); + dstPos = applicator.processLine(srcPos,0,&buf, filter->support(buf.weightsPositionScale().toFloat())); } QRect rc = dev->exactBounds(); @@ -242,10 +262,10 @@ 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 @@ 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 @@ KisFilterWeightsApplicator::LinePos linePos(0,32767); QBENCHMARK { - applicator.processLine(linePos,0,&buf, filter->support()); + applicator.processLine(linePos,0,&buf, filter->support(buf.weightsPositionScale().toFloat())); } }