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 }