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

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
0003  *  SPDX-FileCopyrightText: 2019 Miguel Lopez <reptillia39@live.com>
0004  *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_gradient_painter.h"
0010 
0011 #include <algorithm>
0012 #include <cfloat>
0013 
0014 #include <KoColorSpace.h>
0015 #include <resources/KoAbstractGradient.h>
0016 #include <KoUpdater.h>
0017 
0018 #include <KoColorModelStandardIds.h>
0019 #include <KoColorSpaceRegistry.h>
0020 #include "kis_global.h"
0021 #include "kis_paint_device.h"
0022 #include <resources/KoPattern.h>
0023 #include "kis_selection.h"
0024 
0025 #include <KisSequentialIteratorProgress.h>
0026 #include "kis_image.h"
0027 #include "kis_random_accessor_ng.h"
0028 #include "kis_gradient_shape_strategy.h"
0029 #include "kis_polygonal_gradient_shape_strategy.h"
0030 #include "kis_cached_gradient_shape_strategy.h"
0031 #include "krita_utils.h"
0032 #include "KoMixColorsOp.h"
0033 #include <KisDitherOp.h>
0034 #include <KoCachedGradient.h>
0035 
0036 namespace
0037 {
0038 
0039 class LinearGradientStrategy : public KisGradientShapeStrategy
0040 {
0041 
0042 public:
0043     LinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0044 
0045     double valueAt(double x, double y) const override;
0046 
0047 protected:
0048     double m_normalisedVectorX;
0049     double m_normalisedVectorY;
0050     double m_vectorLength;
0051 };
0052 
0053 LinearGradientStrategy::LinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0054         : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
0055 {
0056     double dx = gradientVectorEnd.x() - gradientVectorStart.x();
0057     double dy = gradientVectorEnd.y() - gradientVectorStart.y();
0058 
0059     m_vectorLength = sqrt((dx * dx) + (dy * dy));
0060 
0061     if (m_vectorLength < DBL_EPSILON) {
0062         m_normalisedVectorX = 0;
0063         m_normalisedVectorY = 0;
0064     } else {
0065         m_normalisedVectorX = dx / m_vectorLength;
0066         m_normalisedVectorY = dy / m_vectorLength;
0067     }
0068 }
0069 
0070 double LinearGradientStrategy::valueAt(double x, double y) const
0071 {
0072     double vx = x - m_gradientVectorStart.x();
0073     double vy = y - m_gradientVectorStart.y();
0074 
0075     // Project the vector onto the normalised gradient vector.
0076     double t = vx * m_normalisedVectorX + vy * m_normalisedVectorY;
0077 
0078     if (m_vectorLength < DBL_EPSILON) {
0079         t = 0;
0080     } else {
0081         // Scale to 0 to 1 over the gradient vector length.
0082         t /= m_vectorLength;
0083     }
0084 
0085     return t;
0086 }
0087 
0088 
0089 class BiLinearGradientStrategy : public LinearGradientStrategy
0090 {
0091 
0092 public:
0093     BiLinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0094 
0095     double valueAt(double x, double y) const override;
0096 };
0097 
0098 BiLinearGradientStrategy::BiLinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0099         : LinearGradientStrategy(gradientVectorStart, gradientVectorEnd)
0100 {
0101 }
0102 
0103 double BiLinearGradientStrategy::valueAt(double x, double y) const
0104 {
0105     double t = LinearGradientStrategy::valueAt(x, y);
0106 
0107     // Reflect
0108     if (t < -DBL_EPSILON) {
0109         t = -t;
0110     }
0111 
0112     return t;
0113 }
0114 
0115 
0116 class RadialGradientStrategy : public KisGradientShapeStrategy
0117 {
0118 
0119 public:
0120     RadialGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0121 
0122     double valueAt(double x, double y) const override;
0123 
0124 protected:
0125     double m_radius;
0126 };
0127 
0128 RadialGradientStrategy::RadialGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0129         : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
0130 {
0131     double dx = gradientVectorEnd.x() - gradientVectorStart.x();
0132     double dy = gradientVectorEnd.y() - gradientVectorStart.y();
0133 
0134     m_radius = sqrt((dx * dx) + (dy * dy));
0135 }
0136 
0137 double RadialGradientStrategy::valueAt(double x, double y) const
0138 {
0139     double dx = x - m_gradientVectorStart.x();
0140     double dy = y - m_gradientVectorStart.y();
0141 
0142     double distance = sqrt((dx * dx) + (dy * dy));
0143 
0144     double t;
0145 
0146     if (m_radius < DBL_EPSILON) {
0147         t = 0;
0148     } else {
0149         t = distance / m_radius;
0150     }
0151 
0152     return t;
0153 }
0154 
0155 
0156 class SquareGradientStrategy : public KisGradientShapeStrategy
0157 {
0158 
0159 public:
0160     SquareGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0161 
0162     double valueAt(double x, double y) const override;
0163 
0164 protected:
0165     double m_normalisedVectorX;
0166     double m_normalisedVectorY;
0167     double m_vectorLength;
0168 };
0169 
0170 SquareGradientStrategy::SquareGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0171         : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
0172 {
0173     double dx = gradientVectorEnd.x() - gradientVectorStart.x();
0174     double dy = gradientVectorEnd.y() - gradientVectorStart.y();
0175 
0176     m_vectorLength = sqrt((dx * dx) + (dy * dy));
0177 
0178     if (m_vectorLength < DBL_EPSILON) {
0179         m_normalisedVectorX = 0;
0180         m_normalisedVectorY = 0;
0181     } else {
0182         m_normalisedVectorX = dx / m_vectorLength;
0183         m_normalisedVectorY = dy / m_vectorLength;
0184     }
0185 }
0186 
0187 double SquareGradientStrategy::valueAt(double x, double y) const
0188 {
0189     double px = x - m_gradientVectorStart.x();
0190     double py = y - m_gradientVectorStart.y();
0191 
0192     double distance1 = 0;
0193     double distance2 = 0;
0194 
0195     double t;
0196 
0197     if (m_vectorLength > DBL_EPSILON) {
0198 
0199         // Point to line distance is:
0200         // distance = ((l0.y() - l1.y()) * p.x() + (l1.x() - l0.x()) * p.y() + l0.x() * l1.y() - l1.x() * l0.y()) / m_vectorLength;
0201         //
0202         // Here l0 = (0, 0) and |l1 - l0| = 1
0203 
0204         distance1 = -m_normalisedVectorY * px + m_normalisedVectorX * py;
0205         distance1 = fabs(distance1);
0206 
0207         // Rotate point by 90 degrees and get the distance to the perpendicular
0208         distance2 = -m_normalisedVectorY * -py + m_normalisedVectorX * px;
0209         distance2 = fabs(distance2);
0210         
0211         t = qMax(distance1, distance2) / m_vectorLength;
0212     } else {
0213         t = 0;
0214     }
0215 
0216     return t;
0217 }
0218 
0219 
0220 class ConicalGradientStrategy : public KisGradientShapeStrategy
0221 {
0222 
0223 public:
0224     ConicalGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0225 
0226     double valueAt(double x, double y) const override;
0227 
0228 protected:
0229     double m_vectorAngle;
0230 };
0231 
0232 ConicalGradientStrategy::ConicalGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0233         : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
0234 {
0235     double dx = gradientVectorEnd.x() - gradientVectorStart.x();
0236     double dy = gradientVectorEnd.y() - gradientVectorStart.y();
0237 
0238     // Get angle from 0 to 2 PI.
0239     m_vectorAngle = atan2(dy, dx) + M_PI;
0240 }
0241 
0242 double ConicalGradientStrategy::valueAt(double x, double y) const
0243 {
0244     double px = x - m_gradientVectorStart.x();
0245     double py = y - m_gradientVectorStart.y();
0246 
0247     double angle = atan2(py, px) + M_PI;
0248 
0249     angle -= m_vectorAngle;
0250 
0251     if (angle < 0) {
0252         angle += 2 * M_PI;
0253     }
0254 
0255     double t = angle / (2 * M_PI);
0256 
0257     return t;
0258 }
0259 
0260 
0261 class ConicalSymetricGradientStrategy : public KisGradientShapeStrategy
0262 {
0263 public:
0264     ConicalSymetricGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0265 
0266     double valueAt(double x, double y) const override;
0267 
0268 protected:
0269     double m_vectorAngle;
0270 };
0271 
0272 ConicalSymetricGradientStrategy::ConicalSymetricGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0273         : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
0274 {
0275     double dx = gradientVectorEnd.x() - gradientVectorStart.x();
0276     double dy = gradientVectorEnd.y() - gradientVectorStart.y();
0277 
0278     // Get angle from 0 to 2 PI.
0279     m_vectorAngle = atan2(dy, dx) + M_PI;
0280 }
0281 
0282 double ConicalSymetricGradientStrategy::valueAt(double x, double y) const
0283 {
0284     double px = x - m_gradientVectorStart.x();
0285     double py = y - m_gradientVectorStart.y();
0286 
0287     double angle = atan2(py, px) + M_PI;
0288 
0289     angle -= m_vectorAngle;
0290 
0291     if (angle < 0) {
0292         angle += 2 * M_PI;
0293     }
0294 
0295     double t;
0296 
0297     if (angle < M_PI) {
0298         t = angle / M_PI;
0299     } else {
0300         t = 1 - ((angle - M_PI) / M_PI);
0301     }
0302 
0303     return t;
0304 }
0305 
0306 class SpiralGradientStrategy : public KisGradientShapeStrategy
0307 {
0308 public:
0309    SpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0310 
0311    double valueAt(double x, double y) const override;
0312 
0313 protected:
0314    double m_vectorAngle;
0315     double m_radius;
0316 };
0317 
0318 SpiralGradientStrategy::SpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0319        : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
0320 {
0321     double dx = gradientVectorEnd.x() - gradientVectorStart.x();
0322     double dy = gradientVectorEnd.y() - gradientVectorStart.y();
0323 
0324     // Get angle from 0 to 2 PI.
0325     m_vectorAngle = atan2(dy, dx) + M_PI;
0326     m_radius = sqrt((dx * dx) + (dy * dy));
0327 };
0328 
0329 double SpiralGradientStrategy::valueAt(double x, double y) const
0330 {
0331     double dx = x - m_gradientVectorStart.x();
0332     double dy = y - m_gradientVectorStart.y();
0333 
0334     double distance = sqrt((dx * dx) + (dy * dy));
0335     double angle = atan2(dy, dx) + M_PI;
0336 
0337     double t;
0338     angle -= m_vectorAngle;
0339 
0340     if (m_radius < DBL_EPSILON) {
0341         t = 0;
0342     } else {
0343         t = distance / m_radius;
0344     }
0345 
0346     if (angle < 0) {
0347         angle += 2 * M_PI;
0348     }
0349 
0350     t += angle / (2 * M_PI);
0351 
0352     return t;
0353 
0354 };
0355 
0356 class ReverseSpiralGradientStrategy : public KisGradientShapeStrategy
0357 {
0358 public:
0359    ReverseSpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
0360 
0361    double valueAt(double x, double y) const override;
0362 
0363 protected:
0364    double m_vectorAngle;
0365     double m_radius;
0366 };
0367 
0368 ReverseSpiralGradientStrategy::ReverseSpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
0369        : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
0370 {
0371     double dx = gradientVectorEnd.x() - gradientVectorStart.x();
0372     double dy = gradientVectorEnd.y() - gradientVectorStart.y();
0373 
0374     // Get angle from 0 to 2 PI.
0375     m_vectorAngle = atan2(dy, dx) + M_PI;
0376     m_radius = sqrt((dx * dx) + (dy * dy));
0377 };
0378 
0379 double ReverseSpiralGradientStrategy::valueAt(double x, double y) const
0380 {
0381     double dx = x - m_gradientVectorStart.x();
0382     double dy = y - m_gradientVectorStart.y();
0383 
0384     double distance = sqrt((dx * dx) + (dy * dy));
0385     double angle = atan2(dy, dx) + M_PI;
0386 
0387     double t;
0388     angle -= m_vectorAngle;
0389 
0390     if (m_radius < DBL_EPSILON) {
0391         t = 0;
0392     } else {
0393         t = distance / m_radius;
0394     }
0395 
0396     if (angle < 0) {
0397         angle += 2 * M_PI;
0398     }
0399 
0400     //Reverse direction of spiral gradient
0401     t += 1 - (angle / (2 * M_PI));
0402 
0403     return t;
0404 
0405 };
0406 
0407 class GradientRepeatStrategy
0408 {
0409 public:
0410     GradientRepeatStrategy() {}
0411     virtual ~GradientRepeatStrategy() {}
0412 
0413     virtual double valueAt(double t) const = 0;
0414 };
0415 
0416 
0417 class GradientRepeatNoneStrategy : public GradientRepeatStrategy
0418 {
0419 public:
0420     static GradientRepeatNoneStrategy *instance();
0421 
0422     double valueAt(double t) const override;
0423 
0424 private:
0425     GradientRepeatNoneStrategy() {}
0426 
0427     static GradientRepeatNoneStrategy *m_instance;
0428 };
0429 
0430 GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::m_instance = 0;
0431 
0432 GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::instance()
0433 {
0434     if (m_instance == 0) {
0435         m_instance = new GradientRepeatNoneStrategy();
0436         Q_CHECK_PTR(m_instance);
0437     }
0438 
0439     return m_instance;
0440 }
0441 
0442 // Output is clamped to 0 to 1.
0443 double GradientRepeatNoneStrategy::valueAt(double t) const
0444 {
0445     double value = t;
0446 
0447     if (t < DBL_EPSILON) {
0448         value = 0;
0449     } else if (t > 1 - DBL_EPSILON) {
0450         value = 1;
0451     }
0452 
0453     return value;
0454 }
0455 
0456 
0457 class GradientRepeatForwardsStrategy : public GradientRepeatStrategy
0458 {
0459 public:
0460     static GradientRepeatForwardsStrategy *instance();
0461 
0462     double valueAt(double t) const override;
0463 
0464 private:
0465     GradientRepeatForwardsStrategy() {}
0466 
0467     static GradientRepeatForwardsStrategy *m_instance;
0468 };
0469 
0470 GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::m_instance = 0;
0471 
0472 GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::instance()
0473 {
0474     if (m_instance == 0) {
0475         m_instance = new GradientRepeatForwardsStrategy();
0476         Q_CHECK_PTR(m_instance);
0477     }
0478 
0479     return m_instance;
0480 }
0481 
0482 // Output is 0 to 1, 0 to 1, 0 to 1...
0483 double GradientRepeatForwardsStrategy::valueAt(double t) const
0484 {
0485     int i = static_cast<int>(t);
0486 
0487     if (t < DBL_EPSILON) {
0488         i--;
0489     }
0490 
0491     double value = t - i;
0492 
0493     return value;
0494 }
0495 
0496 
0497 class GradientRepeatAlternateStrategy : public GradientRepeatStrategy
0498 {
0499 public:
0500     static GradientRepeatAlternateStrategy *instance();
0501 
0502     double valueAt(double t) const override;
0503 
0504 private:
0505     GradientRepeatAlternateStrategy() {}
0506 
0507     static GradientRepeatAlternateStrategy *m_instance;
0508 };
0509 
0510 GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::m_instance = 0;
0511 
0512 GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::instance()
0513 {
0514     if (m_instance == 0) {
0515         m_instance = new GradientRepeatAlternateStrategy();
0516         Q_CHECK_PTR(m_instance);
0517     }
0518 
0519     return m_instance;
0520 }
0521 
0522 // Output is 0 to 1, 1 to 0, 0 to 1, 1 to 0...
0523 double GradientRepeatAlternateStrategy::valueAt(double t) const
0524 {
0525     if (t < 0) {
0526         t = -t;
0527     }
0528 
0529     int i = static_cast<int>(t);
0530 
0531     double value = t - i;
0532 
0533     if (i % 2 == 1) {
0534         value = 1 - value;
0535     }
0536 
0537     return value;
0538 }
0539 //Had to create this class to solve alternating mode for cases where values should be repeated for every HalfValues like for example, spirals...
0540 class GradientRepeatModuloDivisiveContinuousHalfStrategy : public GradientRepeatStrategy
0541 {
0542 public:
0543     static GradientRepeatModuloDivisiveContinuousHalfStrategy *instance();
0544 
0545     double valueAt(double t) const override;
0546 
0547 private:
0548     GradientRepeatModuloDivisiveContinuousHalfStrategy() {}
0549 
0550     static GradientRepeatModuloDivisiveContinuousHalfStrategy *m_instance;
0551 };
0552 
0553 GradientRepeatModuloDivisiveContinuousHalfStrategy *GradientRepeatModuloDivisiveContinuousHalfStrategy::m_instance = 0;
0554 
0555 GradientRepeatModuloDivisiveContinuousHalfStrategy *GradientRepeatModuloDivisiveContinuousHalfStrategy::instance()
0556 {
0557     if (m_instance == 0) {
0558         m_instance = new GradientRepeatModuloDivisiveContinuousHalfStrategy();
0559         Q_CHECK_PTR(m_instance);
0560     }
0561 
0562     return m_instance;
0563 }
0564 
0565 // Output is 0 to 1, 1 to 0, 0 to 1, 1 to 0 per HalfValues
0566 double GradientRepeatModuloDivisiveContinuousHalfStrategy::valueAt(double t) const
0567 {
0568     if (t < 0) {
0569         t = -t;
0570     }
0571 
0572     int i = static_cast<int>(t*2);
0573     int ti = static_cast<int>(t);
0574 
0575     double value = t - ti;
0576 
0577     if (i % 2 == 1) {
0578         value = 1 - value;
0579     }
0580 
0581     return value*2;
0582 }
0583 
0584 class RepeatForwardsPaintPolicy
0585 {
0586 public:
0587     RepeatForwardsPaintPolicy(KisGradientPainter::enumGradientShape shape);
0588 
0589     void setup(const QPointF& gradientVectorStart,
0590                const QPointF& gradientVectorEnd,
0591                const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0592                const GradientRepeatStrategy *repeatStrategy,
0593                qreal antiAliasThreshold,
0594                bool reverseGradient,
0595                const KoCachedGradient * cachedGradient);
0596 
0597     const quint8 *colorAt(qreal x, qreal y) const;
0598 
0599 private:
0600     KisGradientPainter::enumGradientShape m_shape;
0601     qreal m_antiAliasThresholdNormalized {0};
0602     qreal m_antiAliasThresholdNormalizedRev {0};
0603     qreal m_antiAliasThresholdNormalizedDbl {0};
0604     QSharedPointer<KisGradientShapeStrategy> m_shapeStrategy;
0605     const GradientRepeatStrategy *m_repeatStrategy {0};
0606     bool m_reverseGradient {false};
0607     const KoCachedGradient *m_cachedGradient {0};
0608     const quint8 *m_extremeColors[2];
0609     const KoColorSpace *m_colorSpace {0};
0610     mutable QVector<quint8> m_resultColor;
0611 };
0612 
0613 RepeatForwardsPaintPolicy::RepeatForwardsPaintPolicy(KisGradientPainter::enumGradientShape shape)
0614     : m_shape(shape)
0615 {}
0616 
0617 void RepeatForwardsPaintPolicy::setup(const QPointF& gradientVectorStart,
0618                                       const QPointF& gradientVectorEnd,
0619                                       const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0620                                       const GradientRepeatStrategy *repeatStrategy,
0621                                       qreal antiAliasThreshold,
0622                                       bool reverseGradient,
0623                                       const KoCachedGradient * cachedGradient)
0624 {
0625     qreal dx = gradientVectorEnd.x() - gradientVectorStart.x();
0626     qreal dy = gradientVectorEnd.y() - gradientVectorStart.y();
0627     qreal distanceInPixels = sqrt(dx * dx + dy * dy);
0628     // Compute the area to be be smoothed
0629     // based on the length of the gradient
0630     m_antiAliasThresholdNormalized = antiAliasThreshold / distanceInPixels;
0631     m_antiAliasThresholdNormalizedRev = 1. - m_antiAliasThresholdNormalized;
0632     m_antiAliasThresholdNormalizedDbl = 2. * m_antiAliasThresholdNormalized;
0633     
0634     m_shapeStrategy = shapeStrategy;
0635     m_repeatStrategy = repeatStrategy;
0636 
0637     m_reverseGradient = reverseGradient;
0638 
0639     m_cachedGradient = cachedGradient;
0640     m_extremeColors[0] = m_cachedGradient->cachedAt(1.);
0641     m_extremeColors[1] = m_cachedGradient->cachedAt(0.);
0642 
0643     m_colorSpace = m_cachedGradient->colorSpace();
0644 
0645     m_resultColor = QVector<quint8>(m_colorSpace->pixelSize());
0646 }
0647 
0648 const quint8 *RepeatForwardsPaintPolicy::colorAt(qreal x, qreal y) const
0649 {
0650     qreal t = m_shapeStrategy->valueAt(x, y);
0651     // Early return if the pixel is near the center of the gradient if
0652     // the shape is radial or square.
0653     // This prevents applying smoothing since there are
0654     // no aliasing artifacts in these gradient shapes at the center
0655     if (t <= m_antiAliasThresholdNormalized &&
0656         (m_shape == KisGradientPainter::GradientShapeBiLinear ||
0657          m_shape == KisGradientPainter::GradientShapeRadial ||
0658          m_shape == KisGradientPainter::GradientShapeSquare)) {
0659         if (m_reverseGradient) {
0660             t = 1 - t;
0661         }
0662         return m_cachedGradient->cachedAt(t);
0663     }
0664 
0665     t = m_repeatStrategy->valueAt(t);
0666 
0667     if (m_reverseGradient) {
0668         t = 1 - t;
0669     }
0670 
0671     // If this pixel is in the area of the smoothing,
0672     // then perform bilinear interpolation between the extreme colors.
0673     if (t <= m_antiAliasThresholdNormalized || t >= m_antiAliasThresholdNormalizedRev) {
0674         qreal s;
0675         if (t <= m_antiAliasThresholdNormalized) {
0676             s = .5 + t / m_antiAliasThresholdNormalizedDbl;
0677         } else {
0678             s = (t - m_antiAliasThresholdNormalizedRev) / m_antiAliasThresholdNormalizedDbl;
0679         }
0680 
0681         qint16 colorWeights[2];
0682         colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
0683         colorWeights[1] = qint16_MAX - colorWeights[0];
0684 
0685         m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
0686         
0687         return m_resultColor.data();
0688     }
0689 
0690     return m_cachedGradient->cachedAt(t);
0691 }
0692 
0693 class ConicalGradientPaintPolicy
0694 {
0695 public:
0696     void setup(const QPointF& gradientVectorStart,
0697                const QPointF& gradientVectorEnd,
0698                const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0699                const GradientRepeatStrategy *repeatStrategy,
0700                qreal antiAliasThreshold,
0701                bool reverseGradient,
0702                const KoCachedGradient * cachedGradient);
0703 
0704     const quint8 *colorAt(qreal x, qreal y) const;
0705 
0706 private:
0707     QPointF m_gradientVectorStart;
0708     QSharedPointer<KisGradientShapeStrategy> m_shapeStrategy;
0709     const GradientRepeatStrategy *m_repeatStrategy;
0710     qreal m_singularityThreshold;
0711     qreal m_antiAliasThreshold;
0712     bool m_reverseGradient;
0713     const KoCachedGradient *m_cachedGradient;
0714     const quint8 *m_extremeColors[2];
0715     const KoColorSpace *m_colorSpace;
0716     mutable QVector<quint8> m_resultColor;
0717 };
0718 
0719 void ConicalGradientPaintPolicy::setup(const QPointF& gradientVectorStart,
0720                                        const QPointF& gradientVectorEnd,
0721                                        const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0722                                        const GradientRepeatStrategy *repeatStrategy,
0723                                        qreal antiAliasThreshold,
0724                                        bool reverseGradient,
0725                                        const KoCachedGradient * cachedGradient)
0726 {
0727     Q_UNUSED(gradientVectorEnd);
0728 
0729     m_gradientVectorStart = gradientVectorStart;
0730     
0731     m_shapeStrategy = shapeStrategy;
0732     m_repeatStrategy = repeatStrategy;
0733 
0734     m_singularityThreshold = 8.;
0735     m_antiAliasThreshold = antiAliasThreshold;
0736 
0737     m_reverseGradient = reverseGradient;
0738 
0739     m_cachedGradient = cachedGradient;
0740     m_extremeColors[0] = m_cachedGradient->cachedAt(1.);
0741     m_extremeColors[1] = m_cachedGradient->cachedAt(0.);
0742 
0743     m_colorSpace = m_cachedGradient->colorSpace();
0744 
0745     m_resultColor = QVector<quint8>(m_colorSpace->pixelSize());
0746 }
0747 
0748 const quint8 *ConicalGradientPaintPolicy::colorAt(qreal x, qreal y) const
0749 {
0750     // Compute the distance from the center of the gradient to thecurrent pixel
0751     qreal dx = x - m_gradientVectorStart.x();
0752     qreal dy = y - m_gradientVectorStart.y();
0753     qreal distanceInPixels = sqrt(dx * dx + dy * dy);
0754     // Compute the perimeter for this distance
0755     qreal perimeter = 2. * M_PI * distanceInPixels;
0756     // The smoothing is applied in the vicinity of the aliased border.
0757     // The width of the vicinity is an area antiAliasThreshold pixels wide
0758     // to each side of the border, but in this case the area is scaled down
0759     // if it is too close to the center
0760     qreal antiAliasThresholdNormalized;
0761     if (distanceInPixels < m_singularityThreshold){
0762         antiAliasThresholdNormalized = distanceInPixels * m_antiAliasThreshold / m_singularityThreshold;
0763     } else {
0764         antiAliasThresholdNormalized = m_antiAliasThreshold;
0765     }
0766     antiAliasThresholdNormalized = antiAliasThresholdNormalized / perimeter;
0767     qreal antiAliasThresholdNormalizedRev = 1. - antiAliasThresholdNormalized;
0768     qreal antiAliasThresholdNormalizedDbl = 2. * antiAliasThresholdNormalized;
0769 
0770     qreal t = m_shapeStrategy->valueAt(x, y); 
0771     t = m_repeatStrategy->valueAt(t);
0772 
0773     if (m_reverseGradient) {
0774         t = 1 - t;
0775     }
0776 
0777     // If this pixel is in the area of the smoothing,
0778     // then perform bilinear interpolation between the extreme colors.
0779     if (t <= antiAliasThresholdNormalized || t >= antiAliasThresholdNormalizedRev) {
0780         qreal s;
0781         if (t <= antiAliasThresholdNormalized) {
0782             s = .5 + t / antiAliasThresholdNormalizedDbl;
0783         } else {
0784             s = (t - antiAliasThresholdNormalizedRev) / antiAliasThresholdNormalizedDbl;
0785         }
0786 
0787         qint16 colorWeights[2];
0788         colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
0789         colorWeights[1] = qint16_MAX - colorWeights[0];
0790 
0791         m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
0792 
0793         return m_resultColor.data();
0794     }
0795 
0796     return m_cachedGradient->cachedAt(t);
0797 }
0798 
0799 class SpyralGradientRepeatNonePaintPolicy
0800 {
0801 public:
0802     SpyralGradientRepeatNonePaintPolicy(bool isReverseSpiral = false);
0803 
0804     void setup(const QPointF& gradientVectorStart,
0805                const QPointF& gradientVectorEnd,
0806                const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0807                const GradientRepeatStrategy *repeatStrategy,
0808                qreal antiAliasThreshold,
0809                bool reverseGradient,
0810                const KoCachedGradient * cachedGradient);
0811 
0812     const quint8 *colorAt(qreal x, qreal y) const;
0813 
0814 private:
0815     QPointF m_gradientVectorStart;
0816     qreal m_distanceInPixels {0};
0817     qreal m_singularityThreshold {0};
0818     qreal m_angle {0};
0819     QSharedPointer<KisGradientShapeStrategy> m_shapeStrategy;
0820     const GradientRepeatStrategy *m_repeatStrategy {0};
0821     qreal m_antiAliasThreshold {0};
0822     bool m_reverseGradient {false};
0823     const KoCachedGradient *m_cachedGradient {0};
0824     mutable const quint8 *m_extremeColors[2];
0825     const KoColorSpace *m_colorSpace {0};
0826     mutable QVector<quint8> m_resultColor;
0827     bool m_isReverseSpiral {false};
0828 };
0829 
0830 SpyralGradientRepeatNonePaintPolicy::SpyralGradientRepeatNonePaintPolicy(bool isReverseSpiral)
0831     : m_isReverseSpiral(isReverseSpiral)
0832 {
0833 }
0834 
0835 void SpyralGradientRepeatNonePaintPolicy::setup(const QPointF& gradientVectorStart,
0836                                                 const QPointF& gradientVectorEnd,
0837                                                 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0838                                                 const GradientRepeatStrategy *repeatStrategy,
0839                                                 qreal antiAliasThreshold,
0840                                                 bool reverseGradient,
0841                                                 const KoCachedGradient * cachedGradient)
0842 {
0843     m_gradientVectorStart = gradientVectorStart;
0844 
0845     qreal dx = gradientVectorEnd.x() - gradientVectorStart.x();
0846     qreal dy = gradientVectorEnd.y() - gradientVectorStart.y();
0847     m_distanceInPixels = sqrt(dx * dx + dy * dy);
0848     m_singularityThreshold = m_distanceInPixels / 32.;
0849     m_angle = atan2(dy, dx) + M_PI;
0850     
0851     m_shapeStrategy = shapeStrategy;
0852     m_repeatStrategy = repeatStrategy;
0853 
0854     m_antiAliasThreshold = antiAliasThreshold;
0855 
0856     m_reverseGradient = reverseGradient;
0857 
0858     m_cachedGradient = cachedGradient;
0859 
0860     m_colorSpace = m_cachedGradient->colorSpace();
0861 
0862     m_resultColor = QVector<quint8>(m_colorSpace->pixelSize());
0863 }
0864 
0865 const quint8 *SpyralGradientRepeatNonePaintPolicy::colorAt(qreal x, qreal y) const
0866 {
0867     // Compute the distance from the center of the gradient to thecurrent pixel
0868     qreal dx = x - m_gradientVectorStart.x();
0869     qreal dy = y - m_gradientVectorStart.y();
0870     qreal distanceInPixels = sqrt(dx * dx + dy * dy);
0871     // Compute the perimeter for this distance
0872     qreal perimeter = 2. * M_PI * distanceInPixels;
0873     // The smoothing is applied in the vicinity of the aliased border.
0874     // The width of the vicinity is an area antiAliasThreshold pixels wide
0875     // to each side of the border, but in this case the area is scaled down
0876     // if it is too close to the center
0877     qreal antiAliasThresholdNormalized;
0878     if (distanceInPixels < m_singularityThreshold) {
0879         antiAliasThresholdNormalized = distanceInPixels * m_antiAliasThreshold / m_singularityThreshold;
0880     } else {
0881         antiAliasThresholdNormalized = m_antiAliasThreshold;
0882     }
0883     antiAliasThresholdNormalized = antiAliasThresholdNormalized / perimeter;
0884     qreal antiAliasThresholdNormalizedRev = 1. - antiAliasThresholdNormalized;
0885     qreal antiAliasThresholdNormalizedDbl = 2. * antiAliasThresholdNormalized;
0886 
0887     qreal t = m_shapeStrategy->valueAt(x, y); 
0888     t = m_repeatStrategy->valueAt(t);
0889 
0890     if (m_reverseGradient) {
0891         t = 1 - t;
0892     }
0893 
0894     // Compute the area to be be smoothed based on the angle of the gradient
0895     // and the angle of the current pixel to the center of the gradient
0896     qreal angle = atan2(dy, dx) + M_PI;
0897     angle -= m_angle;
0898     if (angle < 0.) {
0899         angle += 2. * M_PI;
0900     }
0901     angle /= (2. * M_PI);
0902     
0903     angle = m_repeatStrategy->valueAt(angle);
0904 
0905     // If this pixel is in the area of the smoothing,
0906     // then perform bilinear interpolation between the extreme colors.
0907     if (distanceInPixels < m_distanceInPixels && (angle <= antiAliasThresholdNormalized || angle >= antiAliasThresholdNormalizedRev)) {
0908         qreal s;
0909         if (angle <= antiAliasThresholdNormalized) {
0910             s = .5 + angle / antiAliasThresholdNormalizedDbl;
0911         } else {
0912             s = (angle - antiAliasThresholdNormalizedRev) / antiAliasThresholdNormalizedDbl;
0913         }
0914 
0915         if (m_reverseGradient) {
0916             distanceInPixels = m_distanceInPixels - distanceInPixels;
0917             m_extremeColors[0] = m_cachedGradient->cachedAt(0.);
0918         } else {
0919             m_extremeColors[0] = m_cachedGradient->cachedAt(1.);
0920         }
0921 
0922         if (m_isReverseSpiral) {
0923             m_extremeColors[1] = m_extremeColors[0];
0924             m_extremeColors[0] = (m_cachedGradient->cachedAt(distanceInPixels / m_distanceInPixels));
0925         } else {
0926             m_extremeColors[1] = (m_cachedGradient->cachedAt(distanceInPixels / m_distanceInPixels));
0927         }
0928 
0929         qint16 colorWeights[2];
0930         colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
0931         colorWeights[1] = qint16_MAX - colorWeights[0];
0932 
0933         m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
0934 
0935         return m_resultColor.data();
0936     }
0937 
0938     return m_cachedGradient->cachedAt(t);
0939 }
0940 
0941 class NoAntialiasPaintPolicy
0942 {
0943 public:
0944     void setup(const QPointF& gradientVectorStart,
0945                const QPointF& gradientVectorEnd,
0946                const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0947                const GradientRepeatStrategy *repeatStrategy,
0948                qreal antiAliasThreshold,
0949                bool reverseGradient,
0950                const KoCachedGradient * cachedGradient);
0951 
0952     const quint8 *colorAt(qreal x, qreal y) const;
0953 
0954 private:
0955     QSharedPointer<KisGradientShapeStrategy> m_shapeStrategy;
0956     const GradientRepeatStrategy *m_repeatStrategy {0};
0957     bool m_reverseGradient {false};
0958     const KoCachedGradient *m_cachedGradient {0};
0959 };
0960 
0961 void NoAntialiasPaintPolicy::setup(const QPointF& gradientVectorStart,
0962                                    const QPointF& gradientVectorEnd,
0963                                    const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
0964                                    const GradientRepeatStrategy *repeatStrategy,
0965                                    qreal antiAliasThreshold,
0966                                    bool reverseGradient,
0967                                    const KoCachedGradient * cachedGradient)
0968 {
0969     Q_UNUSED(gradientVectorStart);
0970     Q_UNUSED(gradientVectorEnd);
0971     Q_UNUSED(antiAliasThreshold);
0972     m_shapeStrategy = shapeStrategy;
0973     m_repeatStrategy = repeatStrategy;
0974     m_reverseGradient = reverseGradient;
0975     m_cachedGradient = cachedGradient;
0976 }
0977 
0978 const quint8 *NoAntialiasPaintPolicy::colorAt(qreal x, qreal y) const
0979 {
0980     qreal t = m_shapeStrategy->valueAt(x, y);
0981     t = m_repeatStrategy->valueAt(t);
0982 
0983     if (m_reverseGradient) {
0984         t = 1 - t;
0985     }
0986 
0987     return m_cachedGradient->cachedAt(t);
0988 }
0989 
0990 }
0991 
0992 struct Q_DECL_HIDDEN KisGradientPainter::Private
0993 {
0994     enumGradientShape shape;
0995 
0996     struct ProcessRegion {
0997         ProcessRegion() {}
0998         ProcessRegion(QSharedPointer<KisGradientShapeStrategy> _precalculatedShapeStrategy,
0999                       const QRect &_processRect)
1000             : precalculatedShapeStrategy(_precalculatedShapeStrategy),
1001               processRect(_processRect) {}
1002 
1003         QSharedPointer<KisGradientShapeStrategy> precalculatedShapeStrategy;
1004         QRect processRect;
1005     };
1006 
1007     QVector<ProcessRegion> processRegions;
1008 };
1009 
1010 KisGradientPainter::KisGradientPainter()
1011     : m_d(new Private())
1012 {
1013 }
1014 
1015 KisGradientPainter::KisGradientPainter(KisPaintDeviceSP device)
1016     : KisPainter(device),
1017       m_d(new Private())
1018 {
1019 }
1020 
1021 KisGradientPainter::KisGradientPainter(KisPaintDeviceSP device, KisSelectionSP selection)
1022     : KisPainter(device, selection),
1023       m_d(new Private())
1024 {
1025 }
1026 
1027 KisGradientPainter::~KisGradientPainter()
1028 {
1029 }
1030 
1031 void KisGradientPainter::setGradientShape(enumGradientShape shape)
1032 {
1033     m_d->shape = shape;
1034 }
1035 
1036 KisGradientShapeStrategy* createPolygonShapeStrategy(const QPainterPath &path, const QRect &boundingRect)
1037 {
1038     // TODO: implement UI for exponent option
1039     const qreal exponent = 2.0;
1040     KisGradientShapeStrategy *strategy =
1041         new KisPolygonalGradientShapeStrategy(path, exponent);
1042 
1043     KIS_ASSERT_RECOVER_NOOP(boundingRect.width() >= 3 &&
1044                             boundingRect.height() >= 3);
1045 
1046     const qreal step =
1047         qMin(qreal(8.0), KritaUtils::maxDimensionPortion(boundingRect, 0.01, 2));
1048 
1049     return new KisCachedGradientShapeStrategy(boundingRect, step, step, strategy);
1050 }
1051 
1052 /**
1053  * TODO: make this call happen asynchronously when the user does nothing
1054  */
1055 void KisGradientPainter::precalculateShape()
1056 {
1057     if (!m_d->processRegions.isEmpty()) return;
1058 
1059     QPainterPath path;
1060 
1061     if (selection()) {
1062         if (!selection()->outlineCacheValid()) {
1063             selection()->recalculateOutlineCache();
1064         }
1065 
1066         KIS_ASSERT_RECOVER_RETURN(selection()->outlineCacheValid());
1067         KIS_ASSERT_RECOVER_RETURN(!selection()->outlineCache().isEmpty());
1068 
1069         path = selection()->outlineCache();
1070     } else {
1071         path.addRect(device()->defaultBounds()->bounds());
1072     }
1073 
1074     QList<QPainterPath> splitPaths = KritaUtils::splitDisjointPaths(path);
1075 
1076     Q_FOREACH (const QPainterPath &subpath, splitPaths) {
1077         QRect boundingRect = subpath.boundingRect().toAlignedRect();
1078 
1079         if (boundingRect.width() < 3 || boundingRect.height() < 3) {
1080             boundingRect = kisGrowRect(boundingRect, 2);
1081         }
1082 
1083         Private::ProcessRegion r(toQShared(createPolygonShapeStrategy(subpath, boundingRect)),
1084                                  boundingRect);
1085         m_d->processRegions << r;
1086     }
1087 }
1088 
1089 bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
1090                                        const QPointF& gradientVectorEnd,
1091                                        enumGradientRepeat repeat,
1092                                        double antiAliasThreshold,
1093                                        bool reverseGradient,
1094                                        qint32 startx,
1095                                        qint32 starty,
1096                                        qint32 width,
1097                                        qint32 height,
1098                                        bool useDithering)
1099 {
1100     return paintGradient(gradientVectorStart,
1101                          gradientVectorEnd,
1102                          repeat,
1103                          antiAliasThreshold,
1104                          reverseGradient,
1105                          QRect(startx, starty, width, height),
1106                          useDithering);
1107 }
1108 
1109 bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
1110                                        const QPointF& gradientVectorEnd,
1111                                        enumGradientRepeat repeat,
1112                                        double antiAliasThreshold,
1113                                        bool reverseGradient,
1114                                        const QRect &applyRect,
1115                                        bool useDithering)
1116 {
1117     // The following combinations of options have aliasing artifacts
1118     // where the first color meets the last color of the gradient.
1119     // so antialias threshold is used to compute if the pixel is in
1120     // the smothing area. Then linear interpolation is used to blend
1121     // between the first and last colors
1122     if (antiAliasThreshold > DBL_EPSILON) {
1123         if ((m_d->shape == GradientShapeLinear || m_d->shape == GradientShapeBiLinear ||
1124             m_d->shape == GradientShapeRadial || m_d->shape == GradientShapeSquare ||
1125             m_d->shape == GradientShapeSpiral || m_d->shape == GradientShapeReverseSpiral)
1126             && repeat == GradientRepeatForwards) {
1127             RepeatForwardsPaintPolicy paintPolicy(m_d->shape);
1128             return paintGradient(gradientVectorStart,
1129                                  gradientVectorEnd,
1130                                  repeat,
1131                                  antiAliasThreshold,
1132                                  reverseGradient,
1133                                  useDithering,
1134                                  applyRect,
1135                                  paintPolicy);
1136 
1137         } else if (m_d->shape == GradientShapeConical) {
1138             ConicalGradientPaintPolicy paintPolicy;
1139             return paintGradient(gradientVectorStart,
1140                                  gradientVectorEnd,
1141                                  repeat,
1142                                  antiAliasThreshold,
1143                                  reverseGradient,
1144                                  useDithering,
1145                                  applyRect,
1146                                  paintPolicy);
1147 
1148         } else if ((m_d->shape == GradientShapeSpiral || m_d->shape == GradientShapeReverseSpiral) &&
1149                    repeat == GradientRepeatNone) {
1150             SpyralGradientRepeatNonePaintPolicy paintPolicy(m_d->shape == GradientShapeReverseSpiral);
1151             return paintGradient(gradientVectorStart,
1152                                  gradientVectorEnd,
1153                                  repeat,
1154                                  antiAliasThreshold,
1155                                  reverseGradient,
1156                                  useDithering,
1157                                  applyRect,
1158                                  paintPolicy);
1159         }
1160     }
1161 
1162     // Default behavior: no antialiasing required
1163     NoAntialiasPaintPolicy paintPolicy;
1164     return paintGradient(gradientVectorStart,
1165                          gradientVectorEnd,
1166                          repeat,
1167                          antiAliasThreshold,
1168                          reverseGradient,
1169                          useDithering,
1170                          applyRect,
1171                          paintPolicy);
1172 }
1173 
1174 template <class T> 
1175 bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
1176                                        const QPointF& gradientVectorEnd,
1177                                        enumGradientRepeat repeat,
1178                                        double antiAliasThreshold,
1179                                        bool reverseGradient,
1180                                        bool useDithering,
1181                                        const QRect &applyRect,
1182                                        T & paintPolicy)
1183 {
1184     if (!gradient()) return false;
1185 
1186     QRect requestedRect = applyRect;
1187 
1188     //If the device has a selection only iterate over that selection united with our area of interest
1189     if (selection()) {
1190         requestedRect &= selection()->selectedExactRect();
1191     }
1192 
1193     QSharedPointer<KisGradientShapeStrategy> shapeStrategy;
1194 
1195     switch (m_d->shape) {
1196     case GradientShapeLinear: {
1197         Private::ProcessRegion r(toQShared(new LinearGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1198                                  requestedRect);
1199         m_d->processRegions.clear();
1200         m_d->processRegions << r;
1201         break;
1202     }
1203     case GradientShapeBiLinear: {
1204         Private::ProcessRegion r(toQShared(new BiLinearGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1205                                  requestedRect);
1206         m_d->processRegions.clear();
1207         m_d->processRegions << r;
1208         break;
1209     }
1210     case GradientShapeRadial: {
1211         Private::ProcessRegion r(toQShared(new RadialGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1212                                  requestedRect);
1213         m_d->processRegions.clear();
1214         m_d->processRegions << r;
1215         break;
1216     }
1217     case GradientShapeSquare: {
1218         Private::ProcessRegion r(toQShared(new SquareGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1219                                  requestedRect);
1220         m_d->processRegions.clear();
1221         m_d->processRegions << r;
1222         break;
1223     }
1224     case GradientShapeConical: {
1225         Private::ProcessRegion r(toQShared(new ConicalGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1226                                  requestedRect);
1227         m_d->processRegions.clear();
1228         m_d->processRegions << r;
1229         break;
1230     }
1231     case GradientShapeConicalSymetric: {
1232         Private::ProcessRegion r(toQShared(new ConicalSymetricGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1233                                  requestedRect);
1234         m_d->processRegions.clear();
1235         m_d->processRegions << r;
1236         break;
1237     }
1238     case GradientShapeSpiral: {
1239         Private::ProcessRegion r(toQShared(new SpiralGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1240                                  requestedRect);
1241         m_d->processRegions.clear();
1242         m_d->processRegions << r;
1243         break;
1244     }
1245     case GradientShapeReverseSpiral: {
1246         Private::ProcessRegion r(toQShared(new ReverseSpiralGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1247                                  requestedRect);
1248         m_d->processRegions.clear();
1249         m_d->processRegions << r;
1250         break;
1251     }
1252     case GradientShapePolygonal:
1253         precalculateShape();
1254         repeat = GradientRepeatNone;
1255         break;
1256     }
1257 
1258     GradientRepeatStrategy *repeatStrategy = 0;
1259 
1260     switch (repeat) {
1261     case GradientRepeatNone:
1262         repeatStrategy = GradientRepeatNoneStrategy::instance();
1263         break;
1264     case GradientRepeatForwards:
1265         repeatStrategy = GradientRepeatForwardsStrategy::instance();
1266         break;
1267     case GradientRepeatAlternate:
1268         if (m_d->shape == GradientShapeSpiral || m_d->shape == GradientShapeReverseSpiral) {repeatStrategy = GradientRepeatModuloDivisiveContinuousHalfStrategy::instance();}
1269         else {repeatStrategy = GradientRepeatAlternateStrategy::instance();}
1270         break;
1271     }
1272     Q_ASSERT(repeatStrategy != 0);
1273 
1274 
1275     KisPaintDeviceSP dev = device()->createCompositionSourceDevice();
1276 
1277     KoID depthId;
1278     const KoColorSpace *destCs = dev->colorSpace();
1279 
1280     if (destCs->colorDepthId() == Integer8BitsColorDepthID) {
1281         depthId = Integer16BitsColorDepthID;
1282     } else {
1283         depthId = destCs->colorDepthId();
1284     }
1285 
1286     const KoColorSpace *mixCs = KoColorSpaceRegistry::instance()->colorSpace(destCs->colorModelId().id(), depthId.id(), destCs->profile());
1287     const quint32 mixPixelSize = mixCs->pixelSize();
1288 
1289     KisPaintDeviceSP tmp(new KisPaintDevice(mixCs));
1290     tmp->setDefaultBounds(dev->defaultBounds());
1291     tmp->clear();
1292 
1293     const KisDitherOp* op = mixCs->ditherOp(destCs->colorDepthId().id(), useDithering ? DITHER_BEST : DITHER_NONE);
1294 
1295     Q_FOREACH (const Private::ProcessRegion &r, m_d->processRegions) {
1296         QRect processRect = r.processRect;
1297         QSharedPointer<KisGradientShapeStrategy> shapeStrategy = r.precalculatedShapeStrategy;
1298 
1299         KoCachedGradient cachedGradient(gradient(), qMax(processRect.width(), processRect.height()), mixCs);
1300 
1301         KisSequentialIteratorProgress it(tmp, processRect, progressUpdater());
1302 
1303         paintPolicy.setup(gradientVectorStart,
1304                           gradientVectorEnd,
1305                           shapeStrategy,
1306                           repeatStrategy,
1307                           antiAliasThreshold,
1308                           reverseGradient,
1309                           &cachedGradient);
1310 
1311         while (it.nextPixel()) {
1312             const quint8 *const pixel {paintPolicy.colorAt(it.x(), it.y())};
1313             memcpy(it.rawData(), pixel, mixPixelSize);
1314         }
1315 
1316         KisRandomAccessorSP dstIt = dev->createRandomAccessorNG();
1317         KisRandomConstAccessorSP srcIt = tmp->createRandomConstAccessorNG();
1318 
1319         int rows = 1;
1320         int columns = 1;
1321 
1322         for (int y = processRect.y(); y <= processRect.bottom(); y += rows) {
1323             rows = qMin(srcIt->numContiguousRows(y), qMin(dstIt->numContiguousRows(y), processRect.bottom() - y + 1));
1324 
1325             for (int x = processRect.x(); x <= processRect.right(); x += columns) {
1326                 columns = qMin(srcIt->numContiguousColumns(x), qMin(dstIt->numContiguousColumns(x), processRect.right() - x + 1));
1327 
1328                 srcIt->moveTo(x, y);
1329                 dstIt->moveTo(x, y);
1330 
1331                 const qint32 srcRowStride = srcIt->rowStride(x, y);
1332                 const qint32 dstRowStride = dstIt->rowStride(x, y);
1333                 const quint8 *srcPtr = srcIt->rawDataConst();
1334                 quint8 *dstPtr = dstIt->rawData();
1335 
1336                 op->dither(srcPtr, srcRowStride, dstPtr, dstRowStride, x, y, columns, rows);
1337             }
1338         }
1339     }
1340 
1341     bitBlt(requestedRect.topLeft(), dev, requestedRect);
1342 
1343     return true;
1344 }