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 }