diff --git a/libs/image/kis_antialiasing_fade_maker.h b/libs/image/kis_antialiasing_fade_maker.h index cc9b05d412..6ef1b25032 100644 --- a/libs/image/kis_antialiasing_fade_maker.h +++ b/libs/image/kis_antialiasing_fade_maker.h @@ -83,6 +83,26 @@ public: return false; } + qreal getRadius(){ + return m_radius; + } + + qreal getAntialiasingFadeStart(){ + return m_antialiasingFadeStart; + } + + qreal getFadeStartValue() { + return m_fadeStartValue; + } + + qreal getAntialiasingFadeCoeff(){ + return m_antialiasingFadeCoeff; + } + + bool getAliasingEnabled(){ + return m_enableAntialiasing; + } + private: qreal m_radius; quint8 m_fadeStartValue; diff --git a/libs/image/kis_brush_mask_applicator_factories.cpp b/libs/image/kis_brush_mask_applicator_factories.cpp index 2c54a61ef8..c8b14e9fd8 100644 --- a/libs/image/kis_brush_mask_applicator_factories.cpp +++ b/libs/image/kis_brush_mask_applicator_factories.cpp @@ -20,6 +20,9 @@ #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" +#include "kis_gauss_circle_mask_generator_p.h" +#include "kis_curve_circle_mask_generator_p.h" + #include "kis_brush_mask_applicators.h" #include "kis_brush_mask_applicator_base.h" @@ -42,6 +45,24 @@ MaskApplicatorFactory::cre return new KisBrushMaskVectorApplicator(maskGenerator); } +template<> +template<> +MaskApplicatorFactory::ReturnType +MaskApplicatorFactory::create(ParamType maskGenerator) +{ + return new KisBrushMaskVectorApplicator(maskGenerator); +} + + +template<> +template<> +MaskApplicatorFactory::ReturnType +MaskApplicatorFactory::create(ParamType maskGenerator) +{ + return new KisBrushMaskVectorApplicator(maskGenerator); +} + + #if defined HAVE_VC struct KisCircleMaskGenerator::FastRowProcessor @@ -134,4 +155,224 @@ FastRowProcessor::process(float* buffer, i } } + +struct KisGaussCircleMaskGenerator::FastRowProcessor +{ + FastRowProcessor(KisGaussCircleMaskGenerator *maskGenerator) + : d(maskGenerator->d.data()) {} + + template + void process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY); + + KisGaussCircleMaskGenerator::Private *d; +}; + +template<> void KisGaussCircleMaskGenerator:: +FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY) +{ + const bool antialiasOn = d->fadeMaker.getAliasingEnabled(); + + float y_ = y - centerY; + float sinay_ = sina * y_; + float cosay_ = cosa * y_; + + float* bufferPointer = buffer; + + Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); + + Vc::float_v increment((float)Vc::float_v::size()); + Vc::float_v vCenterX(centerX); + Vc::float_v vCenter(d->center); + + Vc::float_v vCosa(cosa); + Vc::float_v vSina(sina); + Vc::float_v vCosaY_(cosay_); + Vc::float_v vSinaY_(sinay_); + + Vc::float_v vYCoeff(d->ycoef); + Vc::float_v vDistfactor(d->distfactor); + Vc::float_v vAlphafactor(d->alphafactor); + + Vc::float_v vFadeRadius(d->fadeMaker.getRadius()); + Vc::float_v vFadeStartValue(d->fadeMaker.getFadeStartValue()); + Vc::float_v vFadeAFadeStart(d->fadeMaker.getAntialiasingFadeStart()); + Vc::float_v vFadeAFadeCoeff(d->fadeMaker.getAntialiasingFadeCoeff()); + + Vc::float_v vOne(Vc::One); + Vc::float_v vZero(Vc::Zero); + Vc::float_v vValMax(255.f); + + for (int i=0; i < width; i+= Vc::float_v::size()){ + + Vc::float_v x_ = currentIndices - vCenterX; + + Vc::float_v xr = x_ * vCosa - vSinaY_; + Vc::float_v yr = x_ * vSina + vCosaY_; + + Vc::float_v dist = sqrt(pow2(xr) + pow2(yr * vYCoeff)); + + // BEGIN FadeMaker needFade vectorized + // follow fademaker rules for outsideMask + Vc::float_m outsideMask = dist > vFadeRadius; + dist(outsideMask) = vOne; + + Vc::float_m fadeStartMask(false); + // if antialias is off, do not process + if(antialiasOn){ + fadeStartMask = dist > vFadeAFadeStart; + dist((outsideMask ^ fadeStartMask) & fadeStartMask) = (vFadeStartValue + (dist - vFadeAFadeStart) * vFadeAFadeCoeff) / vValMax; + } + Vc::float_m excludeMask(outsideMask | fadeStartMask); + + if (!excludeMask.isFull()) { + Vc::float_v valDist = dist * vDistfactor; + Vc::float_v fullFade = vAlphafactor * ( d->vErf(valDist + vCenter) - d->vErf(valDist - vCenter)); + + Vc::float_m mask; + // Mask undefined values, out of range are out of mask + mask = Vc::isfinite(fullFade); + fullFade.setZero(!mask); + + // Mask in the inner circe of the mask + mask = fullFade < vZero; + fullFade.setZero(mask); + + // Mask the outter circle + mask = fullFade > 254.974f; + fullFade(mask) = vValMax; + + // Mask (value - value), presicion errors. + Vc::float_v vFade = (vValMax - fullFade) / vValMax; + + // return original dist values before vFade transform + vFade(excludeMask) = dist; + vFade.store(bufferPointer, Vc::Aligned); + + } else { + dist.store(bufferPointer, Vc::Aligned); + } + currentIndices = currentIndices + increment; + + bufferPointer += Vc::float_v::size(); + } +} + +struct KisCurveCircleMaskGenerator::FastRowProcessor +{ + FastRowProcessor(KisCurveCircleMaskGenerator *maskGenerator) + : d(maskGenerator->d.data()) {} + + template + void process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY); + + KisCurveCircleMaskGenerator::Private *d; +}; + + +template<> void KisCurveCircleMaskGenerator:: +FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, + float centerX, float centerY) +{ + const bool antialiasOn = d->fadeMaker.getAliasingEnabled(); + + float y_ = y - centerY; + float sinay_ = sina * y_; + float cosay_ = cosa * y_; + + float* bufferPointer = buffer; + + qreal* curveDataPointer = d->curveData.data(); + + Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); + + Vc::float_v increment((float)Vc::float_v::size()); + Vc::float_v vCenterX(centerX); + + Vc::float_v vCosa(cosa); + Vc::float_v vSina(sina); + Vc::float_v vCosaY_(cosay_); + Vc::float_v vSinaY_(sinay_); + + Vc::float_v vYCoeff(d->ycoef); + Vc::float_v vXCoeff(d->xcoef); + Vc::float_v vCurveResolution(d->curveResolution); + + Vc::float_v vCurvedData(Vc::Zero); + Vc::float_v vCurvedData1(Vc::Zero); + + Vc::float_v vFadeRadius(d->fadeMaker.getRadius()); + Vc::float_v vFadeStartValue(d->fadeMaker.getFadeStartValue()); + Vc::float_v vFadeAFadeStart(d->fadeMaker.getAntialiasingFadeStart()); + Vc::float_v vFadeAFadeCoeff(d->fadeMaker.getAntialiasingFadeCoeff()); + + Vc::float_v vOne(Vc::One); + Vc::float_v vZero(Vc::Zero); + Vc::float_v vValMax(255.f); + + for (int i=0; i < width; i+= Vc::float_v::size()){ + + Vc::float_v x_ = currentIndices - vCenterX; + + Vc::float_v xr = x_ * vCosa - vSinaY_; + Vc::float_v yr = x_ * vSina + vCosaY_; + + Vc::float_v dist = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); + + // BEGIN FadeMaker needFade vectorized + // follow fademaker rules for outsideMask + Vc::float_m outsideMask = dist > vFadeRadius; + dist(outsideMask) = vOne; + + Vc::float_m fadeStartMask(false); + // if antialias is off, do not process + if(antialiasOn){ + fadeStartMask = dist > vFadeAFadeStart; + dist((outsideMask ^ fadeStartMask) & fadeStartMask) = (vFadeStartValue + (dist - vFadeAFadeStart) * vFadeAFadeCoeff) / vValMax; + } + + Vc::float_m excludeMask = outsideMask | fadeStartMask; + + if (!excludeMask.isFull()) { + Vc::float_v valDist = dist * vCurveResolution; + // truncate + Vc::SimdArray vAlphaValue(valDist); + Vc::float_v vFloatAlphaValue = vAlphaValue; + + Vc::float_v alphaValueF = valDist - vFloatAlphaValue; + + vCurvedData.gather(curveDataPointer,vAlphaValue); + vCurvedData1.gather(curveDataPointer,vAlphaValue + 1); +// Vc::float_v vCurvedData1(curveDataPointer,vAlphaValue + 1); + + // vAlpha + Vc::float_v fullFade = ( + (1.0f - alphaValueF) * vCurvedData + + alphaValueF * vCurvedData1); + + Vc::float_m mask; + // Mask in the inner circe of the mask + mask = fullFade < vZero; + fullFade.setZero(mask); + + // Mask outer circle of mask + mask = fullFade >= vOne; + Vc::float_v vFade = (1.0f - fullFade); + vFade.setZero(mask); + + // return original dist values before vFade transform + vFade(excludeMask) = dist; + vFade.store(bufferPointer, Vc::Aligned); + + } else { + dist.store(bufferPointer, Vc::Aligned); + } + currentIndices = currentIndices + increment; + + bufferPointer += Vc::float_v::size(); + } +} + #endif /* defined HAVE_VC */ diff --git a/libs/image/kis_circle_mask_generator.cpp b/libs/image/kis_circle_mask_generator.cpp index a27db05731..6c64dcf3c1 100644 --- a/libs/image/kis_circle_mask_generator.cpp +++ b/libs/image/kis_circle_mask_generator.cpp @@ -139,3 +139,8 @@ void KisCircleMaskGenerator::setSoftness(qreal softness) d->transformedFadeX = d->xfadecoef * safeSoftnessCoeff; d->transformedFadeY = d->yfadecoef * safeSoftnessCoeff; } + +void KisCircleMaskGenerator::resetMaskApplicator(bool forceScalar) +{ + d->applicator.reset(createOptimizedClass >(this,forceScalar)); +} diff --git a/libs/image/kis_circle_mask_generator.h b/libs/image/kis_circle_mask_generator.h index c6aaa04143..0fdb389d9a 100644 --- a/libs/image/kis_circle_mask_generator.h +++ b/libs/image/kis_circle_mask_generator.h @@ -49,6 +49,8 @@ public: void setSoftness(qreal softness) override; void setScale(qreal scaleX, qreal scaleY) override; + void resetMaskApplicator(bool forceScalar); + private: qreal norme(qreal a, qreal b) const { diff --git a/libs/image/kis_curve_circle_mask_generator.cpp b/libs/image/kis_curve_circle_mask_generator.cpp index 4df3be0f07..77a39565a7 100644 --- a/libs/image/kis_curve_circle_mask_generator.cpp +++ b/libs/image/kis_curve_circle_mask_generator.cpp @@ -17,9 +17,27 @@ */ #include //MSVC requires that Vc come first - #include +#include +#ifdef HAVE_VC +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wlocal-type-template-args" +#endif +#if defined _MSC_VER +// Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' +#pragma warning ( push ) +#pragma warning ( disable : 4244 ) +#pragma warning ( disable : 4800 ) +#endif +#include +#include +#if defined _MSC_VER +#pragma warning ( pop ) +#endif +#endif + #include #include #include @@ -29,38 +47,13 @@ #include "kis_fast_math.h" #include "kis_base_mask_generator.h" -#include "kis_curve_circle_mask_generator.h" -#include "kis_cubic_curve.h" #include "kis_antialiasing_fade_maker.h" +#include "kis_brush_mask_applicator_factories.h" +#include "kis_curve_circle_mask_generator.h" +#include "kis_curve_circle_mask_generator_p.h" +#include "kis_cubic_curve.h" -struct Q_DECL_HIDDEN KisCurveCircleMaskGenerator::Private -{ - Private(bool enableAntialiasing) - : fadeMaker(*this, enableAntialiasing) - { - } - - Private(const Private &rhs) - : xcoef(rhs.xcoef), - ycoef(rhs.ycoef), - curveResolution(rhs.curveResolution), - curveData(rhs.curveData), - curvePoints(rhs.curvePoints), - dirty(true), - fadeMaker(rhs.fadeMaker,*this) - { - } - - qreal xcoef, ycoef; - qreal curveResolution; - QVector curveData; - QList curvePoints; - bool dirty; - - KisAntialiasingFadeMaker1D fadeMaker; - inline quint8 value(qreal dist) const; -}; KisCurveCircleMaskGenerator::KisCurveCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, const KisCubicCurve &curve, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, CIRCLE, SoftId), d(new Private(antialiasEdges)) @@ -73,12 +66,15 @@ KisCurveCircleMaskGenerator::KisCurveCircleMaskGenerator(qreal diameter, qreal r d->dirty = false; setScale(1.0, 1.0); + + d->applicator.reset(createOptimizedClass >(this)); } KisCurveCircleMaskGenerator::KisCurveCircleMaskGenerator(const KisCurveCircleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { + d->applicator.reset(createOptimizedClass >(this)); } KisCurveCircleMaskGenerator::~KisCurveCircleMaskGenerator() @@ -108,6 +104,16 @@ bool KisCurveCircleMaskGenerator::shouldSupersample() const return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; } +bool KisCurveCircleMaskGenerator::shouldVectorize() const +{ + return !shouldSupersample() && spikes() == 2; +} + +KisBrushMaskApplicatorBase* KisCurveCircleMaskGenerator::applicator() +{ + return d->applicator.data(); +} + inline quint8 KisCurveCircleMaskGenerator::Private::value(qreal dist) const { qreal distance = dist * curveResolution; @@ -118,6 +124,7 @@ inline quint8 KisCurveCircleMaskGenerator::Private::value(qreal dist) const qreal alpha = ( (1.0 - alphaValueF) * curveData.at(alphaValue) + alphaValueF * curveData.at(alphaValue+1)); + return (1.0 - alpha) * 255; } @@ -178,3 +185,8 @@ void KisCurveCircleMaskGenerator::transformCurveForSoftness(qreal softness,const KisCubicCurve curve(newList); result = curve.floatTransfer( curveResolution ); } + +void KisCurveCircleMaskGenerator::resetMaskApplicator(bool forceScalar) +{ + d->applicator.reset(createOptimizedClass >(this,forceScalar)); +} diff --git a/libs/image/kis_curve_circle_mask_generator.h b/libs/image/kis_curve_circle_mask_generator.h index a5db1ae129..1b75196209 100644 --- a/libs/image/kis_curve_circle_mask_generator.h +++ b/libs/image/kis_curve_circle_mask_generator.h @@ -19,11 +19,12 @@ #ifndef _KIS_CURVE_CIRCLE_MASK_GENERATOR_H_ #define _KIS_CURVE_CIRCLE_MASK_GENERATOR_H_ -#include -#include "kritaimage_export.h" - #include #include +#include + +#include "kritaimage_export.h" +#include "kis_mask_generator.h" class KisCubicCurve; class QDomElement; @@ -38,7 +39,8 @@ class QPointF; */ class KRITAIMAGE_EXPORT KisCurveCircleMaskGenerator : public KisMaskGenerator { - +public: + struct FastRowProcessor; public: KisCurveCircleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes,const KisCubicCurve& curve, bool antialiasEdges); @@ -55,6 +57,11 @@ public: void toXML(QDomDocument& , QDomElement&) const override; void setSoftness(qreal softness) override; + bool shouldVectorize() const override; + KisBrushMaskApplicatorBase* applicator() override; + + void resetMaskApplicator(bool forceScalar); + static void transformCurveForSoftness(qreal softness,const QList &points, int curveResolution, QVector &result); private: diff --git a/libs/image/kis_curve_circle_mask_generator_p.h b/libs/image/kis_curve_circle_mask_generator_p.h new file mode 100644 index 0000000000..2497963b62 --- /dev/null +++ b/libs/image/kis_curve_circle_mask_generator_p.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010 Lukáš Tvrdý + * + * 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_CURVE_CIRCLE_MASK_GENERATOR_P_H +#define KIS_CURVE_CIRCLE_MASK_GENERATOR_P_H + +#include "kis_antialiasing_fade_maker.h" +#include "kis_brush_mask_applicator_base.h" + +struct Q_DECL_HIDDEN KisCurveCircleMaskGenerator::Private +{ + Private(bool enableAntialiasing) + : fadeMaker(*this, enableAntialiasing) + { + } + + Private(const Private &rhs) + : xcoef(rhs.xcoef), + ycoef(rhs.ycoef), + curveResolution(rhs.curveResolution), + curveData(rhs.curveData), + curvePoints(rhs.curvePoints), + dirty(true), + fadeMaker(rhs.fadeMaker,*this) + { + } + + qreal xcoef, ycoef; + qreal curveResolution; + QVector curveData; + QList curvePoints; + bool dirty; + + KisAntialiasingFadeMaker1D fadeMaker; + QScopedPointer applicator; + + inline quint8 value(qreal dist) const; +}; + +#endif // KIS_CURVE_CIRCLE_MASK_GENERATOR_P_H diff --git a/libs/image/kis_gauss_circle_mask_generator.cpp b/libs/image/kis_gauss_circle_mask_generator.cpp index 50977c95f6..91e08a7cd7 100644 --- a/libs/image/kis_gauss_circle_mask_generator.cpp +++ b/libs/image/kis_gauss_circle_mask_generator.cpp @@ -20,6 +20,25 @@ #include //MSVC requires that Vc come first #include +#include +#ifdef HAVE_VC +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wlocal-type-template-args" +#endif +#if defined _MSC_VER +// Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' +#pragma warning ( push ) +#pragma warning ( disable : 4244 ) +#pragma warning ( disable : 4800 ) +#endif +#include +#include +#if defined _MSC_VER +#pragma warning ( pop ) +#endif +#endif + #include #include #include @@ -29,8 +48,11 @@ #include "kis_fast_math.h" #include "kis_base_mask_generator.h" -#include "kis_gauss_circle_mask_generator.h" #include "kis_antialiasing_fade_maker.h" +#include "kis_brush_mask_applicator_factories.h" +#include "kis_brush_mask_applicator_base.h" +#include "kis_gauss_circle_mask_generator.h" +#include "kis_gauss_circle_mask_generator_p.h" #define M_SQRT_2 1.41421356237309504880 @@ -41,47 +63,28 @@ #endif -struct Q_DECL_HIDDEN KisGaussCircleMaskGenerator::Private -{ - Private(bool enableAntialiasing) - : fadeMaker(*this, enableAntialiasing) - { - } - - Private(const Private &rhs) - : ycoef(rhs.ycoef), - fade(rhs.fade), - center(rhs.center), - distfactor(rhs.distfactor), - alphafactor(rhs.alphafactor), - fadeMaker(rhs.fadeMaker, *this) - { - } - - qreal ycoef; - qreal fade; - qreal center, distfactor, alphafactor; - KisAntialiasingFadeMaker1D fadeMaker; - - inline quint8 value(qreal dist) const; -}; - KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, CIRCLE, GaussId), d(new Private(antialiasEdges)) { d->ycoef = 1.0 / ratio; d->fade = 1.0 - (fh + fv) / 2.0; + if (d->fade == 0.0) d->fade = 1e-6; else if (d->fade == 1.0) d->fade = 1.0 - 1e-6; // would become undefined for fade == 0 or 1 + d->center = (2.5 * (6761.0*d->fade-10000.0))/(M_SQRT_2*6761.0*d->fade); d->alphafactor = 255.0 / (2.0 * erf(d->center)); + + d->applicator.reset(createOptimizedClass >(this)); + } KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(const KisGaussCircleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { + d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisGaussCircleMaskGenerator::clone() const @@ -109,6 +112,21 @@ inline quint8 KisGaussCircleMaskGenerator::Private::value(qreal dist) const return (quint8) 255 - ret; } +bool KisGaussCircleMaskGenerator::shouldSupersample() const +{ + return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; +} + +bool KisGaussCircleMaskGenerator::shouldVectorize() const +{ + return !shouldSupersample() && spikes() == 2; +} + +KisBrushMaskApplicatorBase* KisGaussCircleMaskGenerator::applicator() +{ + return d->applicator.data(); +} + quint8 KisGaussCircleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; @@ -125,3 +143,8 @@ quint8 KisGaussCircleMaskGenerator::valueAt(qreal x, qreal y) const return d->value(dist); } + +void KisGaussCircleMaskGenerator::resetMaskApplicator(bool forceScalar) +{ + d->applicator.reset(createOptimizedClass >(this,forceScalar)); +} diff --git a/libs/image/kis_gauss_circle_mask_generator.h b/libs/image/kis_gauss_circle_mask_generator.h index ae6158c9ee..1872641f48 100644 --- a/libs/image/kis_gauss_circle_mask_generator.h +++ b/libs/image/kis_gauss_circle_mask_generator.h @@ -20,16 +20,18 @@ #ifndef _KIS_GAUSS_MASK_GENERATOR_H_ #define _KIS_GAUSS_MASK_GENERATOR_H_ -#include #include "kritaimage_export.h" +#include "kis_mask_generator.h" +#include /** * This mask generator uses a Gaussian-blurred circle */ class KRITAIMAGE_EXPORT KisGaussCircleMaskGenerator : public KisMaskGenerator { - +public: + struct FastRowProcessor; public: KisGaussCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges); @@ -41,6 +43,13 @@ public: void setScale(qreal scaleX, qreal scaleY) override; + bool shouldSupersample() const override; + + bool shouldVectorize() const override; + KisBrushMaskApplicatorBase* applicator() override; + + void resetMaskApplicator(bool forceScalar); + private: qreal norme(qreal a, qreal b) const { diff --git a/libs/image/kis_gauss_circle_mask_generator_p.h b/libs/image/kis_gauss_circle_mask_generator_p.h new file mode 100644 index 0000000000..ff139f5039 --- /dev/null +++ b/libs/image/kis_gauss_circle_mask_generator_p.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2008-2009 Cyrille Berger + * + * 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_GAUSS_MASK_GENERATOR_P_H_ +#define _KIS_GAUSS_MASK_GENERATOR_P_H_ + +#include "kis_antialiasing_fade_maker.h" +#include "kis_brush_mask_applicator_base.h" + +struct Q_DECL_HIDDEN KisGaussCircleMaskGenerator::Private +{ + Private(bool enableAntialiasing) + : fadeMaker(*this, enableAntialiasing) + { + } + + Private(const Private &rhs) + : ycoef(rhs.ycoef), + fade(rhs.fade), + center(rhs.center), + distfactor(rhs.distfactor), + alphafactor(rhs.alphafactor), + fadeMaker(rhs.fadeMaker, *this) + { + } + + qreal ycoef; + qreal fade; + qreal center; + qreal distfactor; + qreal alphafactor; + KisAntialiasingFadeMaker1D fadeMaker; + + QScopedPointer applicator; + + inline quint8 value(qreal dist) const; + + #if defined HAVE_VC + // vectorized erf function, precision 1e-5 + Vc::float_v vErf(Vc::float_v x) { + Vc::float_v xa = abs(x); + Vc::float_m precisionLimit(xa >= 9.3f); // wrong result for any number beyond this + xa(precisionLimit) = 0; + Vc::float_v sign(Vc::One); + Vc::float_m invertMask = x < 0.f; + sign(invertMask) = -1.f; + + // CONSTANTS + float a1 = 0.254829592; + float a2 = -0.284496736; + float a3 = 1.421413741; + float a4 = -1.453152027; + float a5 = 1.061405429; + float p = 0.3275911; + + Vc::float_v t = 1.0f / (1.0f + p * xa); + Vc::float_v y = 1.0f - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * exp(-xa * xa); + y(precisionLimit) = 1.0f; + return sign * y; + } + #endif /* defined HAVE_VC */ +}; + +#endif /* _KIS_GAUSS_MASK_GENERATOR_P_H_ */ diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt index 90a5e6289e..979f93f1dd 100644 --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -124,6 +124,8 @@ ecm_add_tests( kis_marker_painter_test.cpp kis_lazy_brush_test.cpp kis_colorize_mask_test.cpp + kis_mask_similarity_test.cpp + KisMaskGeneratorBenchmark.cpp NAME_PREFIX "krita-image-" LINK_LIBRARIES kritaimage Qt5::Test) diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.cpp b/libs/image/tests/KisMaskGeneratorBenchmark.cpp new file mode 100644 index 0000000000..6488fdb237 --- /dev/null +++ b/libs/image/tests/KisMaskGeneratorBenchmark.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018 Iván Santa María + * + * 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 +#include +#include +#include + +#include "KisMaskGeneratorBenchmark.h" + +#include "kis_mask_similarity_test.h" + +#include "kis_brush_mask_applicator_base.h" +#include "kis_mask_generator.h" +#include "kis_cubic_curve.h" +#include "krita_utils.h" + +#include "testutil.h" + +class KisMaskGeneratorBenchmarkTester +{ +public: + KisMaskGeneratorBenchmarkTester(KisBrushMaskApplicatorBase *_applicatorBase, QRect _bounds) + : applicatorBase(_applicatorBase) + , m_bounds(_bounds) + { + KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); + m_paintDev->setRect(m_bounds); + m_paintDev->initialize(255); + + MaskProcessingData data(m_paintDev, m_colorSpace, + 0.0, 1.0, + m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); + + // Start Benchmark + applicatorBase->initializeData(&data); + + QElapsedTimer maskGenerationTime; + maskGenerationTime.start(); + + QBENCHMARK { + + applicatorBase->process(m_bounds); + } + } + +protected: + const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + QRect m_bounds; + + KisBrushMaskApplicatorBase *applicatorBase; + KisFixedPaintDeviceSP m_paintDev; +}; + +void KisMaskGeneratorBenchmark::testDefaultScalarMask() +{ + QRect bounds(0,0,1000,1000); + { + KisCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testDefaultVectorMask() +{ + QRect bounds(0,0,1000,1000); + { + KisCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); + KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testCircularGaussScalarMask() +{ + QRect bounds(0,0,1000,1000); + { + KisGaussCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); + circScalar.setDiameter(1000); + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testCircularGaussVectorMask() +{ + QRect bounds(0,0,1000,1000); + { + KisGaussCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); + circVectr.setDiameter(1000); + KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testCircularSoftScalarMask() +{ + QRect bounds(0,0,1000,1000); + KisCubicCurve pointsCurve; + pointsCurve.fromString(QString("0,1;1,0")); + { + KisCurveCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); + circScalar.setSoftness(0.5); + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + + KisMaskGeneratorBenchmarkTester(circScalar.applicator(), bounds); + } +} + +void KisMaskGeneratorBenchmark::testCircularSoftVectorMask() +{ + QRect bounds(0,0,1000,1000); + KisCubicCurve pointsCurve; + pointsCurve.fromString(QString("0,1;1,0")); + { + KisCurveCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); + circVectr.setSoftness(0.5); + KisMaskGeneratorBenchmarkTester(circVectr.applicator(), bounds); + } +} + +QTEST_MAIN(KisMaskGeneratorBenchmark) diff --git a/libs/image/tests/KisMaskGeneratorBenchmark.h b/libs/image/tests/KisMaskGeneratorBenchmark.h new file mode 100644 index 0000000000..86318b1605 --- /dev/null +++ b/libs/image/tests/KisMaskGeneratorBenchmark.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Iván Santa María + * + * 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 KISMASKGENERATORBENCHMARK_H +#define KISMASKGENERATORBENCHMARK_H + +#include + +class KisMaskGeneratorBenchmark : public QObject +{ + Q_OBJECT +private Q_SLOTS: + + void testDefaultScalarMask(); + void testDefaultVectorMask(); + + void testCircularGaussScalarMask(); + void testCircularGaussVectorMask(); + + void testCircularSoftScalarMask(); + void testCircularSoftVectorMask(); +}; + +#endif // KISMASKGENERATORBENCHMARK_H diff --git a/libs/image/tests/kis_mask_similarity_test.cpp b/libs/image/tests/kis_mask_similarity_test.cpp new file mode 100644 index 0000000000..ae13ffb33d --- /dev/null +++ b/libs/image/tests/kis_mask_similarity_test.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2018 Iván Santa María + * + * 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_mask_similarity_test.h" + +#include +#include + +#include +#include + +#include "kis_brush_mask_applicator_base.h" +#include "kis_mask_generator.h" +#include "kis_cubic_curve.h" +#include "krita_utils.h" + +enum MaskType { + DEFAULT, CIRC_GAUSS, CIRC_SOFT, RECT_GAUSS, RECT_SOFT, STAMP +}; + +class KisMaskSimilarityTester +{ + +public: + KisMaskSimilarityTester(KisBrushMaskApplicatorBase *_legacy, KisBrushMaskApplicatorBase *_vectorized, QRect _bounds, MaskType type, bool renderImage = true) + : legacy(_legacy) + , vectorized(_vectorized) + , m_bounds(_bounds) + { + KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); + m_paintDev->setRect(m_bounds); + m_paintDev->initialize(255); + + MaskProcessingData data(m_paintDev, m_colorSpace, + 0.0, 1.0, + m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); + + // Start legacy scalar processing + legacy->initializeData(&data); + legacy->process(m_bounds); + + QImage scalarImage(m_paintDev->convertToQImage(m_colorSpace->profile())); + scalarImage.invertPixels(); // Make pixel color black + + // Start vector processing + m_paintDev->initialize(255); + vectorized->initializeData(&data); + vectorized->process(m_bounds); + + QImage vectorImage(m_paintDev->convertToQImage(m_colorSpace->profile())); + vectorImage.invertPixels(); // Make pixel color black + + if (renderImage) { + scalarImage.save(QString(getTypeName(type) + "_scalar_mask.png"),"PNG"); + vectorImage.save(QString(getTypeName(type) +"_vector_mask.png"),"PNG"); + } + + // Check for differences, max errors: 0 + QPoint tmpPt; + QVERIFY(TestUtil::compareQImages(tmpPt,scalarImage, vectorImage, 0, 2, 0)); + } + +private: + QString getTypeName(MaskType type) { + QString strName; + switch (type) { + case CIRC_GAUSS: + strName = "CircGauss"; + break; + case CIRC_SOFT: + strName = "CircSoft"; + break; + case RECT_GAUSS: + strName = "RectGauss"; + break; + case RECT_SOFT: + strName = "RectSoft"; + break; + case STAMP: + strName = "Stamp"; + break; + default: + strName = "Default"; + break; + } + return strName; + } + +protected: + const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); + + KisBrushMaskApplicatorBase *legacy; + KisBrushMaskApplicatorBase *vectorized; + QRect m_bounds; + KisFixedPaintDeviceSP m_paintDev; +}; + + +void KisMaskSimilarityTest::testCircleMask() +{ + QRect bounds(0,0,500,500); + { + KisCircleMaskGenerator circVectr(500, 1.0, 0.5, 0.5, 2, true); + KisCircleMaskGenerator circScalar(circVectr); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, DEFAULT); + } +} + +void KisMaskSimilarityTest::testGaussCircleMask() +{ + QRect bounds(0,0,500,500); + { + KisGaussCircleMaskGenerator circVectr(500, 1.0, .8, .2, 2, true); + circVectr.setDiameter(500); + KisGaussCircleMaskGenerator circScalar(circVectr); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, CIRC_GAUSS); + } + // Exahustive test + for (size_t i = 0; i <= 100; i += 3){ + for (size_t j = 0; j <= 100; j += 3){ + for (size_t k = 0; k <= 100; k += 15){ + { + KisGaussCircleMaskGenerator circVectr(500, k/100.f, i/100.f, j/100.f, 2, true); + circVectr.setDiameter(500); + KisGaussCircleMaskGenerator circScalar(circVectr); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds,CIRC_GAUSS,false); + } + } } } // end for +} + +void KisMaskSimilarityTest::testSoftCircleMask() +{ + QRect bounds(0,0,500,500); + KisCubicCurve pointsCurve; + pointsCurve.fromString(QString("0,1;1,0")); + { + KisCurveCircleMaskGenerator circVectr(500, 1.0, 0.5, 0.5, 2, pointsCurve,true); + circVectr.setDiameter(500); + // circVectr.setSoftness(1.0); + KisCurveCircleMaskGenerator circScalar(circVectr); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds, CIRC_SOFT); + } + + // Exahustive test + for (size_t i = 0; i <= 100; i += 3){ + for (size_t j = 0; j <= 100; j += 3){ + for (size_t k = 0; k <= 100; k += 15){ + { + KisCurveCircleMaskGenerator circVectr(500, k/100.f, i/100.f, j/100.f, 2,pointsCurve, true); + circVectr.setDiameter(500); + KisCurveCircleMaskGenerator circScalar(circVectr); + + circScalar.resetMaskApplicator(true); // Force usage of scalar backend + KisMaskSimilarityTester(circScalar.applicator(), circVectr.applicator(), bounds,CIRC_SOFT,false); + } + } } } // end for +} + +QTEST_MAIN(KisMaskSimilarityTest) diff --git a/libs/image/tests/kis_mask_similarity_test.h b/libs/image/tests/kis_mask_similarity_test.h new file mode 100644 index 0000000000..231f2de921 --- /dev/null +++ b/libs/image/tests/kis_mask_similarity_test.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Iván Santa María + * + * 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_MASK_SIMILARITY_TEST +#define KIS_MASK_SIMILARITY_TEST + +#include + +class KisMaskSimilarityTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + + void testCircleMask(); + void testGaussCircleMask(); + void testSoftCircleMask(); +}; + +#endif diff --git a/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h b/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h index 2bdd069a74..f81fbea0f6 100644 --- a/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h +++ b/libs/pigment/compositeops/KoVcMultiArchBuildSupport.h @@ -109,4 +109,14 @@ createOptimizedClass(typename FactoryType::ParamType param) } +template +typename FactoryType::ReturnType +createOptimizedClass(typename FactoryType::ParamType param, bool forceScalarImplemetation) +{ + if(forceScalarImplemetation){ + return FactoryType::template create(param); + } + return createOptimizedClass(param); +} + #endif /* __KOVCMULTIARCHBUILDSUPPORT_H */