File indexing completed on 2024-05-12 15:58:09

0001 /*
0002  *  SPDX-FileCopyrightText: 2004, 2007-2009 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0004  *  SPDX-FileCopyrightText: 2011 Sven Langkamp <sven.langkamp@gmail.com>
0005  *  SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
0006  *
0007  *  SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 
0010 #include <compositeops/KoMultiArchBuildSupport.h>
0011 
0012 #include "kis_brush_mask_applicator_factories.h"
0013 #include "kis_mask_generator.h"
0014 #include "kis_brush_mask_applicator_base.h"
0015 
0016 #include <cmath>
0017 #include "kis_fast_math.h"
0018 
0019 #include <QDomDocument>
0020 
0021 #include "kis_circle_mask_generator.h"
0022 #include "kis_rect_mask_generator.h"
0023 #include "kis_gauss_circle_mask_generator.h"
0024 #include "kis_gauss_rect_mask_generator.h"
0025 #include "kis_cubic_curve.h"
0026 #include "kis_curve_circle_mask_generator.h"
0027 #include "kis_curve_rect_mask_generator.h"
0028 #include <kis_dom_utils.h>
0029 
0030 struct KisMaskGenerator::Private {
0031     Private()
0032         : diameter(1.0),
0033           ratio(1.0),
0034           softness(1.0),
0035           fh(1.0),
0036           fv(1.0),
0037           cs(0.0),
0038           ss(0.0),
0039           cachedSpikesAngle(0.0),
0040           spikes(2),
0041           empty(true),
0042           antialiasEdges(false),
0043           type(CIRCLE),
0044           scaleX(1.0),
0045           scaleY(1.0)
0046     {
0047     }
0048 
0049     Private(const Private &rhs)
0050         : diameter(rhs.diameter),
0051           ratio(rhs.ratio),
0052           softness(rhs.softness),
0053           fh(rhs.fh),
0054           fv(rhs.fv),
0055           cs(rhs.cs),
0056           ss(rhs.ss),
0057           cachedSpikesAngle(rhs.cachedSpikesAngle),
0058           spikes(rhs.spikes),
0059           empty(rhs.empty),
0060           antialiasEdges(rhs.antialiasEdges),
0061           type(rhs.type),
0062           curveString(rhs.curveString),
0063           scaleX(rhs.scaleX),
0064           scaleY(rhs.scaleY)
0065     {
0066     }
0067 
0068     qreal diameter, ratio;
0069     qreal softness;
0070     qreal fh, fv;
0071     qreal cs, ss;
0072     qreal cachedSpikesAngle;
0073     int spikes;
0074     bool empty;
0075     bool antialiasEdges;
0076     Type type;
0077     QString curveString;
0078     qreal scaleX;
0079     qreal scaleY;
0080     QScopedPointer<KisBrushMaskApplicatorBase> defaultMaskProcessor;
0081 };
0082 
0083 
0084 KisMaskGenerator::KisMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges, Type type, const KoID& id)
0085     : d(new Private), m_id(id)
0086 {
0087     d->diameter = diameter;
0088     d->ratio = ratio;
0089     d->fh = 0.5 * fh;
0090     d->fv = 0.5 * fv;
0091     d->softness = 1.0; // by default don't change fade/softness/hardness
0092     d->spikes = spikes;
0093     d->cachedSpikesAngle = M_PI / d->spikes;
0094     d->type = type;
0095     d->antialiasEdges = antialiasEdges;
0096     d->scaleX = 1.0;
0097     d->scaleY = 1.0;
0098     init();
0099 }
0100 
0101 KisMaskGenerator::~KisMaskGenerator()
0102 {
0103 }
0104 
0105 KisMaskGenerator::KisMaskGenerator(const KisMaskGenerator &rhs)
0106     : d(new Private(*rhs.d)),
0107       m_id(rhs.m_id)
0108 {
0109 }
0110 
0111 void KisMaskGenerator::init()
0112 {
0113     d->cs = cos(- 2 * M_PI / d->spikes);
0114     d->ss = sin(- 2 * M_PI / d->spikes);
0115     d->empty = (d->ratio == 0.0 || d->diameter == 0.0);
0116 }
0117 
0118 bool KisMaskGenerator::shouldSupersample() const
0119 {
0120     return antialiasEdges() && (effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10);
0121 }
0122 
0123 bool KisMaskGenerator::shouldVectorize() const
0124 {
0125     return false;
0126 }
0127 
0128 
0129 bool KisMaskGenerator::isEmpty() const
0130 {
0131     return d->empty;
0132 }
0133 
0134 KisBrushMaskApplicatorBase* KisMaskGenerator::applicator()
0135 {
0136     if (!d->defaultMaskProcessor) {
0137         d->defaultMaskProcessor.reset(
0138             createOptimizedClass<MaskApplicatorFactory<KisMaskGenerator>>(this));
0139     }
0140 
0141     return d->defaultMaskProcessor.data();
0142 }
0143 
0144 void KisMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const
0145 {
0146     Q_UNUSED(doc);
0147     //e.setAttribute("radius", d->radius);
0148     e.setAttribute("diameter", QString::number(d->diameter));
0149     e.setAttribute("ratio", QString::number(d->ratio));
0150     e.setAttribute("hfade", QString::number(horizontalFade()));
0151     e.setAttribute("vfade", QString::number(verticalFade()));
0152     e.setAttribute("spikes", d->spikes);
0153     e.setAttribute("type", d->type == CIRCLE ? "circle" : "rect");
0154     e.setAttribute("antialiasEdges", d->antialiasEdges);
0155     e.setAttribute("id", id());
0156 }
0157 
0158 KisMaskGenerator* KisMaskGenerator::fromXML(const QDomElement& elt)
0159 {
0160     double diameter = 1.0;
0161     // backward compatibility -- it was mistakenly named radius for 2.2
0162     if (elt.hasAttribute("radius")){
0163         diameter = KisDomUtils::toDouble(elt.attribute("radius", "1.0"));
0164     }
0165     else /*if (elt.hasAttribute("diameter"))*/{
0166         diameter = KisDomUtils::toDouble(elt.attribute("diameter", "1.0"));
0167     }
0168     double ratio = KisDomUtils::toDouble(elt.attribute("ratio", "1.0"));
0169     double hfade = KisDomUtils::toDouble(elt.attribute("hfade", "0.0"));
0170     double vfade = KisDomUtils::toDouble(elt.attribute("vfade", "0.0"));
0171 
0172     int spikes = elt.attribute("spikes", "2").toInt();
0173     QString typeShape = elt.attribute("type", "circle");
0174     QString id = elt.attribute("id", DefaultId.id());
0175     bool antialiasEdges = elt.attribute("antialiasEdges", "0").toInt();
0176 
0177     if (id == DefaultId.id()) {
0178         if (typeShape == "circle") {
0179             return new KisCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0180         } else {
0181             return new KisRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0182         }
0183     }
0184 
0185     if (id == SoftId.id()) {
0186         KisCubicCurve curve;
0187         curve.fromString(elt.attribute("softness_curve","0,0;1,1"));
0188 
0189         if (typeShape == "circle") {
0190             return new KisCurveCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, curve, antialiasEdges);
0191         } else {
0192             return new KisCurveRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, curve, antialiasEdges);
0193         }
0194     }
0195 
0196     if (id == GaussId.id()) {
0197         if (typeShape == "circle") {
0198             return new KisGaussCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0199         } else {
0200             return new KisGaussRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0201         }
0202     }
0203 
0204     // if unknown
0205     return new KisCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, true);
0206 }
0207 
0208 qreal KisMaskGenerator::width() const
0209 {
0210     return d->diameter;
0211 }
0212 
0213 qreal KisMaskGenerator::height() const
0214 {
0215     if (d->spikes == 2) {
0216         return d->diameter * d->ratio;
0217     }
0218     return d->diameter;
0219 }
0220 
0221 qreal KisMaskGenerator::effectiveSrcWidth() const
0222 {
0223     return d->diameter * d->scaleX;
0224 }
0225 
0226 qreal KisMaskGenerator::effectiveSrcHeight() const
0227 {
0228     /**
0229      * This height is related to the source of the brush mask, so we
0230      * don't take spikes into account, they will be generated from
0231      * this data.
0232      */
0233     return d->diameter * d->ratio * d->scaleY;
0234 }
0235 
0236 qreal KisMaskGenerator::diameter() const
0237 {
0238     return d->diameter;
0239 }
0240 
0241 void KisMaskGenerator::setDiameter(qreal value)
0242 {
0243     d->diameter = value;
0244     init();
0245     setScale(d->scaleX, d->scaleY);
0246 }
0247 
0248 qreal KisMaskGenerator::ratio() const
0249 {
0250     return d->ratio;
0251 }
0252 
0253 qreal KisMaskGenerator::softness() const
0254 {
0255     return d->softness;
0256 }
0257 
0258 
0259 void KisMaskGenerator::setSoftness(qreal softness)
0260 {
0261     d->softness = softness;
0262 }
0263 
0264 
0265 qreal KisMaskGenerator::horizontalFade() const
0266 {
0267     return 2.0 * d->fh; // 'cause in init we divide it again
0268 }
0269 
0270 qreal KisMaskGenerator::verticalFade() const
0271 {
0272     return 2.0 * d->fv; // 'cause in init we divide it again
0273 }
0274 
0275 int KisMaskGenerator::spikes() const
0276 {
0277     return d->spikes;
0278 }
0279 
0280 KisMaskGenerator::Type KisMaskGenerator::type() const
0281 {
0282     return d->type;
0283 }
0284 
0285 QList< KoID > KisMaskGenerator::maskGeneratorIds()
0286 {
0287     QList<KoID> ids;
0288     ids << DefaultId << SoftId << GaussId;
0289     return ids;
0290 }
0291 
0292 QString KisMaskGenerator::curveString() const
0293 {
0294     return d->curveString;
0295 }
0296 
0297 void KisMaskGenerator::setCurveString(const QString& curveString)
0298 {
0299     d->curveString = curveString;
0300 }
0301 
0302 bool KisMaskGenerator::antialiasEdges() const
0303 {
0304     return d->antialiasEdges;
0305 }
0306 
0307 void KisMaskGenerator::setScale(qreal scaleX, qreal scaleY)
0308 {
0309     d->scaleX = scaleX;
0310     d->scaleY = scaleY;
0311 }
0312 
0313 void KisMaskGenerator::fixRotation(qreal &xr, qreal &yr) const
0314 {
0315     if (d->spikes > 2) {
0316         double angle = (KisFastMath::atan2(yr, xr));
0317 
0318         while (angle > d->cachedSpikesAngle){
0319             double sx = xr;
0320             double sy = yr;
0321 
0322             xr = d->cs * sx - d->ss * sy;
0323             yr = d->ss * sx + d->cs * sy;
0324 
0325             angle -= 2 * d->cachedSpikesAngle;
0326         }
0327     }
0328 }