File indexing completed on 2024-05-19 04:26:07

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 <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 };
0081 
0082 
0083 KisMaskGenerator::KisMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges, Type type, const KoID& id)
0084     : d(new Private), m_id(id)
0085 {
0086     d->diameter = diameter;
0087     d->ratio = ratio;
0088     d->fh = 0.5 * fh;
0089     d->fv = 0.5 * fv;
0090     d->softness = 1.0; // by default don't change fade/softness/hardness
0091     d->spikes = spikes;
0092     d->cachedSpikesAngle = M_PI / d->spikes;
0093     d->type = type;
0094     d->antialiasEdges = antialiasEdges;
0095     d->scaleX = 1.0;
0096     d->scaleY = 1.0;
0097     init();
0098 }
0099 
0100 KisMaskGenerator::~KisMaskGenerator()
0101 {
0102 }
0103 
0104 KisMaskGenerator::KisMaskGenerator(const KisMaskGenerator &rhs)
0105     : d(new Private(*rhs.d)),
0106       m_id(rhs.m_id)
0107 {
0108 }
0109 
0110 void KisMaskGenerator::init()
0111 {
0112     d->cs = cos(- 2 * M_PI / d->spikes);
0113     d->ss = sin(- 2 * M_PI / d->spikes);
0114     d->empty = (d->ratio == 0.0 || d->diameter == 0.0);
0115 }
0116 
0117 bool KisMaskGenerator::shouldSupersample() const
0118 {
0119     return antialiasEdges() && (effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10);
0120 }
0121 
0122 bool KisMaskGenerator::shouldSupersample6x6() const
0123 {
0124     return effectiveSrcWidth() < 1 || effectiveSrcHeight() < 1;
0125 }
0126 
0127 bool KisMaskGenerator::shouldVectorize() const
0128 {
0129     return false;
0130 }
0131 
0132 
0133 bool KisMaskGenerator::isEmpty() const
0134 {
0135     return d->empty;
0136 }
0137 
0138 void KisMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const
0139 {
0140     Q_UNUSED(doc);
0141     //e.setAttribute("radius", d->radius);
0142     e.setAttribute("diameter", QString::number(d->diameter));
0143     e.setAttribute("ratio", QString::number(d->ratio));
0144     e.setAttribute("hfade", QString::number(horizontalFade()));
0145     e.setAttribute("vfade", QString::number(verticalFade()));
0146     e.setAttribute("spikes", d->spikes);
0147     e.setAttribute("type", d->type == CIRCLE ? "circle" : "rect");
0148     e.setAttribute("antialiasEdges", d->antialiasEdges);
0149     e.setAttribute("id", id());
0150 }
0151 
0152 KisMaskGenerator* KisMaskGenerator::fromXML(const QDomElement& elt)
0153 {
0154     double diameter = 1.0;
0155     // backward compatibility -- it was mistakenly named radius for 2.2
0156     if (elt.hasAttribute("radius")){
0157         diameter = KisDomUtils::toDouble(elt.attribute("radius", "1.0"));
0158     }
0159     else /*if (elt.hasAttribute("diameter"))*/{
0160         diameter = KisDomUtils::toDouble(elt.attribute("diameter", "1.0"));
0161     }
0162     double ratio = KisDomUtils::toDouble(elt.attribute("ratio", "1.0"));
0163     double hfade = KisDomUtils::toDouble(elt.attribute("hfade", "0.0"));
0164     double vfade = KisDomUtils::toDouble(elt.attribute("vfade", "0.0"));
0165 
0166     int spikes = elt.attribute("spikes", "2").toInt();
0167     QString typeShape = elt.attribute("type", "circle");
0168     QString id = elt.attribute("id", DefaultId.id());
0169     bool antialiasEdges = elt.attribute("antialiasEdges", "0").toInt();
0170 
0171     if (id == DefaultId.id()) {
0172         if (typeShape == "circle") {
0173             return new KisCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0174         } else {
0175             return new KisRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0176         }
0177     }
0178 
0179     if (id == SoftId.id()) {
0180         const KisCubicCurve curve(elt.attribute("softness_curve","0,0;1,1"));
0181 
0182         if (typeShape == "circle") {
0183             return new KisCurveCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, curve, antialiasEdges);
0184         } else {
0185             return new KisCurveRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, curve, antialiasEdges);
0186         }
0187     }
0188 
0189     if (id == GaussId.id()) {
0190         if (typeShape == "circle") {
0191             return new KisGaussCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0192         } else {
0193             return new KisGaussRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges);
0194         }
0195     }
0196 
0197     // if unknown
0198     return new KisCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, true);
0199 }
0200 
0201 qreal KisMaskGenerator::width() const
0202 {
0203     return d->diameter;
0204 }
0205 
0206 qreal KisMaskGenerator::height() const
0207 {
0208     if (d->spikes == 2) {
0209         return d->diameter * d->ratio;
0210     }
0211     return d->diameter;
0212 }
0213 
0214 qreal KisMaskGenerator::effectiveSrcWidth() const
0215 {
0216     return d->diameter * d->scaleX;
0217 }
0218 
0219 qreal KisMaskGenerator::effectiveSrcHeight() const
0220 {
0221     /**
0222      * This height is related to the source of the brush mask, so we
0223      * don't take spikes into account, they will be generated from
0224      * this data.
0225      */
0226     return d->diameter * d->ratio * d->scaleY;
0227 }
0228 
0229 qreal KisMaskGenerator::diameter() const
0230 {
0231     return d->diameter;
0232 }
0233 
0234 void KisMaskGenerator::setDiameter(qreal value)
0235 {
0236     d->diameter = value;
0237     init();
0238     setScale(d->scaleX, d->scaleY);
0239 }
0240 
0241 qreal KisMaskGenerator::ratio() const
0242 {
0243     return d->ratio;
0244 }
0245 
0246 qreal KisMaskGenerator::softness() const
0247 {
0248     return d->softness;
0249 }
0250 
0251 
0252 void KisMaskGenerator::setSoftness(qreal softness)
0253 {
0254     d->softness = softness;
0255 }
0256 
0257 
0258 qreal KisMaskGenerator::horizontalFade() const
0259 {
0260     return 2.0 * d->fh; // 'cause in init we divide it again
0261 }
0262 
0263 qreal KisMaskGenerator::verticalFade() const
0264 {
0265     return 2.0 * d->fv; // 'cause in init we divide it again
0266 }
0267 
0268 int KisMaskGenerator::spikes() const
0269 {
0270     return d->spikes;
0271 }
0272 
0273 KisMaskGenerator::Type KisMaskGenerator::type() const
0274 {
0275     return d->type;
0276 }
0277 
0278 QList< KoID > KisMaskGenerator::maskGeneratorIds()
0279 {
0280     QList<KoID> ids;
0281     ids << DefaultId << SoftId << GaussId;
0282     return ids;
0283 }
0284 
0285 QString KisMaskGenerator::curveString() const
0286 {
0287     return d->curveString;
0288 }
0289 
0290 void KisMaskGenerator::setCurveString(const QString& curveString)
0291 {
0292     d->curveString = curveString;
0293 }
0294 
0295 bool KisMaskGenerator::antialiasEdges() const
0296 {
0297     return d->antialiasEdges;
0298 }
0299 
0300 void KisMaskGenerator::setScale(qreal scaleX, qreal scaleY)
0301 {
0302     d->scaleX = scaleX;
0303     d->scaleY = scaleY;
0304 }
0305 
0306 void KisMaskGenerator::fixRotation(qreal &xr, qreal &yr) const
0307 {
0308     if (d->spikes > 2) {
0309         double angle = (KisFastMath::atan2(yr, xr));
0310 
0311         while (angle > d->cachedSpikesAngle){
0312             double sx = xr;
0313             double sy = yr;
0314 
0315             xr = d->cs * sx - d->ss * sy;
0316             yr = d->ss * sx + d->cs * sy;
0317 
0318             angle -= 2 * d->cachedSpikesAngle;
0319         }
0320     }
0321 }