Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_gradient_painter.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
3 * SPDX-FileCopyrightText: 2019 Miguel Lopez <reptillia39@live.com>
4 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
10
11#include <algorithm>
12#include <cfloat>
13
14#include <KoColorSpace.h>
16#include <KoUpdater.h>
17
20#include "kis_global.h"
21#include "kis_paint_device.h"
22#include <resources/KoPattern.h>
23#include "kis_selection.h"
24
26#include "kis_image.h"
31#include "krita_utils.h"
32#include "KoMixColorsOp.h"
33#include <KisDitherOp.h>
34#include <KoCachedGradient.h>
35
36namespace
37{
38
39class LinearGradientStrategy : public KisGradientShapeStrategy
40{
41
42public:
43 LinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
44
45 double valueAt(double x, double y) const override;
46
47protected:
48 double m_normalisedVectorX;
49 double m_normalisedVectorY;
50 double m_vectorLength;
51};
52
53LinearGradientStrategy::LinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
54 : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
55{
56 double dx = gradientVectorEnd.x() - gradientVectorStart.x();
57 double dy = gradientVectorEnd.y() - gradientVectorStart.y();
58
59 m_vectorLength = sqrt((dx * dx) + (dy * dy));
60
61 if (m_vectorLength < DBL_EPSILON) {
62 m_normalisedVectorX = 0;
63 m_normalisedVectorY = 0;
64 } else {
65 m_normalisedVectorX = dx / m_vectorLength;
66 m_normalisedVectorY = dy / m_vectorLength;
67 }
68}
69
70double LinearGradientStrategy::valueAt(double x, double y) const
71{
72 double vx = x - m_gradientVectorStart.x();
73 double vy = y - m_gradientVectorStart.y();
74
75 // Project the vector onto the normalised gradient vector.
76 double t = vx * m_normalisedVectorX + vy * m_normalisedVectorY;
77
78 if (m_vectorLength < DBL_EPSILON) {
79 t = 0;
80 } else {
81 // Scale to 0 to 1 over the gradient vector length.
82 t /= m_vectorLength;
83 }
84
85 return t;
86}
87
88
89class BiLinearGradientStrategy : public LinearGradientStrategy
90{
91
92public:
93 BiLinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
94
95 double valueAt(double x, double y) const override;
96};
97
98BiLinearGradientStrategy::BiLinearGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
99 : LinearGradientStrategy(gradientVectorStart, gradientVectorEnd)
100{
101}
102
103double BiLinearGradientStrategy::valueAt(double x, double y) const
104{
105 double t = LinearGradientStrategy::valueAt(x, y);
106
107 // Reflect
108 if (t < -DBL_EPSILON) {
109 t = -t;
110 }
111
112 return t;
113}
114
115
116class RadialGradientStrategy : public KisGradientShapeStrategy
117{
118
119public:
120 RadialGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
121
122 double valueAt(double x, double y) const override;
123
124protected:
125 double m_radius;
126};
127
128RadialGradientStrategy::RadialGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
129 : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
130{
131 double dx = gradientVectorEnd.x() - gradientVectorStart.x();
132 double dy = gradientVectorEnd.y() - gradientVectorStart.y();
133
134 m_radius = sqrt((dx * dx) + (dy * dy));
135}
136
137double RadialGradientStrategy::valueAt(double x, double y) const
138{
139 double dx = x - m_gradientVectorStart.x();
140 double dy = y - m_gradientVectorStart.y();
141
142 double distance = sqrt((dx * dx) + (dy * dy));
143
144 double t;
145
146 if (m_radius < DBL_EPSILON) {
147 t = 0;
148 } else {
149 t = distance / m_radius;
150 }
151
152 return t;
153}
154
155
156class SquareGradientStrategy : public KisGradientShapeStrategy
157{
158
159public:
160 SquareGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
161
162 double valueAt(double x, double y) const override;
163
164protected:
165 double m_normalisedVectorX;
166 double m_normalisedVectorY;
167 double m_vectorLength;
168};
169
170SquareGradientStrategy::SquareGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
171 : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
172{
173 double dx = gradientVectorEnd.x() - gradientVectorStart.x();
174 double dy = gradientVectorEnd.y() - gradientVectorStart.y();
175
176 m_vectorLength = sqrt((dx * dx) + (dy * dy));
177
178 if (m_vectorLength < DBL_EPSILON) {
179 m_normalisedVectorX = 0;
180 m_normalisedVectorY = 0;
181 } else {
182 m_normalisedVectorX = dx / m_vectorLength;
183 m_normalisedVectorY = dy / m_vectorLength;
184 }
185}
186
187double SquareGradientStrategy::valueAt(double x, double y) const
188{
189 double px = x - m_gradientVectorStart.x();
190 double py = y - m_gradientVectorStart.y();
191
192 double distance1 = 0;
193 double distance2 = 0;
194
195 double t;
196
197 if (m_vectorLength > DBL_EPSILON) {
198
199 // Point to line distance is:
200 // distance = ((l0.y() - l1.y()) * p.x() + (l1.x() - l0.x()) * p.y() + l0.x() * l1.y() - l1.x() * l0.y()) / m_vectorLength;
201 //
202 // Here l0 = (0, 0) and |l1 - l0| = 1
203
204 distance1 = -m_normalisedVectorY * px + m_normalisedVectorX * py;
205 distance1 = fabs(distance1);
206
207 // Rotate point by 90 degrees and get the distance to the perpendicular
208 distance2 = -m_normalisedVectorY * -py + m_normalisedVectorX * px;
209 distance2 = fabs(distance2);
210
211 t = qMax(distance1, distance2) / m_vectorLength;
212 } else {
213 t = 0;
214 }
215
216 return t;
217}
218
219
220class ConicalGradientStrategy : public KisGradientShapeStrategy
221{
222
223public:
224 ConicalGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
225
226 double valueAt(double x, double y) const override;
227
228protected:
229 double m_vectorAngle;
230};
231
232ConicalGradientStrategy::ConicalGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
233 : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
234{
235 double dx = gradientVectorEnd.x() - gradientVectorStart.x();
236 double dy = gradientVectorEnd.y() - gradientVectorStart.y();
237
238 // Get angle from 0 to 2 PI.
239 m_vectorAngle = atan2(dy, dx) + M_PI;
240}
241
242double ConicalGradientStrategy::valueAt(double x, double y) const
243{
244 double px = x - m_gradientVectorStart.x();
245 double py = y - m_gradientVectorStart.y();
246
247 double angle = atan2(py, px) + M_PI;
248
249 angle -= m_vectorAngle;
250
251 if (angle < 0) {
252 angle += 2 * M_PI;
253 }
254
255 double t = angle / (2 * M_PI);
256
257 return t;
258}
259
260
261class ConicalSymetricGradientStrategy : public KisGradientShapeStrategy
262{
263public:
264 ConicalSymetricGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
265
266 double valueAt(double x, double y) const override;
267
268protected:
269 double m_vectorAngle;
270};
271
272ConicalSymetricGradientStrategy::ConicalSymetricGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
273 : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
274{
275 double dx = gradientVectorEnd.x() - gradientVectorStart.x();
276 double dy = gradientVectorEnd.y() - gradientVectorStart.y();
277
278 // Get angle from 0 to 2 PI.
279 m_vectorAngle = atan2(dy, dx) + M_PI;
280}
281
282double ConicalSymetricGradientStrategy::valueAt(double x, double y) const
283{
284 double px = x - m_gradientVectorStart.x();
285 double py = y - m_gradientVectorStart.y();
286
287 double angle = atan2(py, px) + M_PI;
288
289 angle -= m_vectorAngle;
290
291 if (angle < 0) {
292 angle += 2 * M_PI;
293 }
294
295 double t;
296
297 if (angle < M_PI) {
298 t = angle / M_PI;
299 } else {
300 t = 1 - ((angle - M_PI) / M_PI);
301 }
302
303 return t;
304}
305
306class SpiralGradientStrategy : public KisGradientShapeStrategy
307{
308public:
309 SpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
310
311 double valueAt(double x, double y) const override;
312
313protected:
314 double m_vectorAngle;
315 double m_radius;
316};
317
318SpiralGradientStrategy::SpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
319 : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
320{
321 double dx = gradientVectorEnd.x() - gradientVectorStart.x();
322 double dy = gradientVectorEnd.y() - gradientVectorStart.y();
323
324 // Get angle from 0 to 2 PI.
325 m_vectorAngle = atan2(dy, dx) + M_PI;
326 m_radius = sqrt((dx * dx) + (dy * dy));
327};
328
329double SpiralGradientStrategy::valueAt(double x, double y) const
330{
331 double dx = x - m_gradientVectorStart.x();
332 double dy = y - m_gradientVectorStart.y();
333
334 double distance = sqrt((dx * dx) + (dy * dy));
335 double angle = atan2(dy, dx) + M_PI;
336
337 double t;
338 angle -= m_vectorAngle;
339
340 if (m_radius < DBL_EPSILON) {
341 t = 0;
342 } else {
343 t = distance / m_radius;
344 }
345
346 if (angle < 0) {
347 angle += 2 * M_PI;
348 }
349
350 t += angle / (2 * M_PI);
351
352 return t;
353
354};
355
356class ReverseSpiralGradientStrategy : public KisGradientShapeStrategy
357{
358public:
359 ReverseSpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd);
360
361 double valueAt(double x, double y) const override;
362
363protected:
364 double m_vectorAngle;
365 double m_radius;
366};
367
368ReverseSpiralGradientStrategy::ReverseSpiralGradientStrategy(const QPointF& gradientVectorStart, const QPointF& gradientVectorEnd)
369 : KisGradientShapeStrategy(gradientVectorStart, gradientVectorEnd)
370{
371 double dx = gradientVectorEnd.x() - gradientVectorStart.x();
372 double dy = gradientVectorEnd.y() - gradientVectorStart.y();
373
374 // Get angle from 0 to 2 PI.
375 m_vectorAngle = atan2(dy, dx) + M_PI;
376 m_radius = sqrt((dx * dx) + (dy * dy));
377};
378
379double ReverseSpiralGradientStrategy::valueAt(double x, double y) const
380{
381 double dx = x - m_gradientVectorStart.x();
382 double dy = y - m_gradientVectorStart.y();
383
384 double distance = sqrt((dx * dx) + (dy * dy));
385 double angle = atan2(dy, dx) + M_PI;
386
387 double t;
388 angle -= m_vectorAngle;
389
390 if (m_radius < DBL_EPSILON) {
391 t = 0;
392 } else {
393 t = distance / m_radius;
394 }
395
396 if (angle < 0) {
397 angle += 2 * M_PI;
398 }
399
400 //Reverse direction of spiral gradient
401 t += 1 - (angle / (2 * M_PI));
402
403 return t;
404
405};
406
407class GradientRepeatStrategy
408{
409public:
410 GradientRepeatStrategy() {}
411 virtual ~GradientRepeatStrategy() {}
412
413 virtual double valueAt(double t) const = 0;
414};
415
416
417class GradientRepeatNoneStrategy : public GradientRepeatStrategy
418{
419public:
420 static GradientRepeatNoneStrategy *instance();
421
422 double valueAt(double t) const override;
423
424private:
425 GradientRepeatNoneStrategy() {}
426
427 static GradientRepeatNoneStrategy *m_instance;
428};
429
430GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::m_instance = 0;
431
432GradientRepeatNoneStrategy *GradientRepeatNoneStrategy::instance()
433{
434 if (m_instance == 0) {
435 m_instance = new GradientRepeatNoneStrategy();
436 Q_CHECK_PTR(m_instance);
437 }
438
439 return m_instance;
440}
441
442// Output is clamped to 0 to 1.
443double GradientRepeatNoneStrategy::valueAt(double t) const
444{
445 double value = t;
446
447 if (t < DBL_EPSILON) {
448 value = 0;
449 } else if (t > 1 - DBL_EPSILON) {
450 value = 1;
451 }
452
453 return value;
454}
455
456
457class GradientRepeatForwardsStrategy : public GradientRepeatStrategy
458{
459public:
460 static GradientRepeatForwardsStrategy *instance();
461
462 double valueAt(double t) const override;
463
464private:
465 GradientRepeatForwardsStrategy() {}
466
467 static GradientRepeatForwardsStrategy *m_instance;
468};
469
470GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::m_instance = 0;
471
472GradientRepeatForwardsStrategy *GradientRepeatForwardsStrategy::instance()
473{
474 if (m_instance == 0) {
475 m_instance = new GradientRepeatForwardsStrategy();
476 Q_CHECK_PTR(m_instance);
477 }
478
479 return m_instance;
480}
481
482// Output is 0 to 1, 0 to 1, 0 to 1...
483double GradientRepeatForwardsStrategy::valueAt(double t) const
484{
485 int i = static_cast<int>(t);
486
487 if (t < DBL_EPSILON) {
488 i--;
489 }
490
491 double value = t - i;
492
493 return value;
494}
495
496
497class GradientRepeatAlternateStrategy : public GradientRepeatStrategy
498{
499public:
500 static GradientRepeatAlternateStrategy *instance();
501
502 double valueAt(double t) const override;
503
504private:
505 GradientRepeatAlternateStrategy() {}
506
507 static GradientRepeatAlternateStrategy *m_instance;
508};
509
510GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::m_instance = 0;
511
512GradientRepeatAlternateStrategy *GradientRepeatAlternateStrategy::instance()
513{
514 if (m_instance == 0) {
515 m_instance = new GradientRepeatAlternateStrategy();
516 Q_CHECK_PTR(m_instance);
517 }
518
519 return m_instance;
520}
521
522// Output is 0 to 1, 1 to 0, 0 to 1, 1 to 0...
523double GradientRepeatAlternateStrategy::valueAt(double t) const
524{
525 if (t < 0) {
526 t = -t;
527 }
528
529 int i = static_cast<int>(t);
530
531 double value = t - i;
532
533 if (i % 2 == 1) {
534 value = 1 - value;
535 }
536
537 return value;
538}
539//Had to create this class to solve alternating mode for cases where values should be repeated for every HalfValues like for example, spirals...
540class GradientRepeatModuloDivisiveContinuousHalfStrategy : public GradientRepeatStrategy
541{
542public:
543 static GradientRepeatModuloDivisiveContinuousHalfStrategy *instance();
544
545 double valueAt(double t) const override;
546
547private:
548 GradientRepeatModuloDivisiveContinuousHalfStrategy() {}
549
550 static GradientRepeatModuloDivisiveContinuousHalfStrategy *m_instance;
551};
552
553GradientRepeatModuloDivisiveContinuousHalfStrategy *GradientRepeatModuloDivisiveContinuousHalfStrategy::m_instance = 0;
554
555GradientRepeatModuloDivisiveContinuousHalfStrategy *GradientRepeatModuloDivisiveContinuousHalfStrategy::instance()
556{
557 if (m_instance == 0) {
558 m_instance = new GradientRepeatModuloDivisiveContinuousHalfStrategy();
559 Q_CHECK_PTR(m_instance);
560 }
561
562 return m_instance;
563}
564
565// Output is 0 to 1, 1 to 0, 0 to 1, 1 to 0 per HalfValues
566double GradientRepeatModuloDivisiveContinuousHalfStrategy::valueAt(double t) const
567{
568 if (t < 0) {
569 t = -t;
570 }
571
572 int i = static_cast<int>(t*2);
573 int ti = static_cast<int>(t);
574
575 double value = t - ti;
576
577 if (i % 2 == 1) {
578 value = 1 - value;
579 }
580
581 return value*2;
582}
583
584class RepeatForwardsPaintPolicy
585{
586public:
587 RepeatForwardsPaintPolicy(KisGradientPainter::enumGradientShape shape);
588
589 void setup(const QPointF& gradientVectorStart,
590 const QPointF& gradientVectorEnd,
591 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
592 const GradientRepeatStrategy *repeatStrategy,
593 qreal antiAliasThreshold,
594 bool reverseGradient,
595 const KoCachedGradient * cachedGradient);
596
597 const quint8 *colorAt(qreal x, qreal y) const;
598
599private:
601 qreal m_antiAliasThresholdNormalized {0};
602 qreal m_antiAliasThresholdNormalizedRev {0};
603 qreal m_antiAliasThresholdNormalizedDbl {0};
605 const GradientRepeatStrategy *m_repeatStrategy {0};
606 bool m_reverseGradient {false};
607 const KoCachedGradient *m_cachedGradient {0};
608 const quint8 *m_extremeColors[2];
609 const KoColorSpace *m_colorSpace {0};
610 mutable QVector<quint8> m_resultColor;
611};
612
613RepeatForwardsPaintPolicy::RepeatForwardsPaintPolicy(KisGradientPainter::enumGradientShape shape)
614 : m_shape(shape)
615{}
616
617void RepeatForwardsPaintPolicy::setup(const QPointF& gradientVectorStart,
618 const QPointF& gradientVectorEnd,
619 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
620 const GradientRepeatStrategy *repeatStrategy,
621 qreal antiAliasThreshold,
622 bool reverseGradient,
623 const KoCachedGradient * cachedGradient)
624{
625 qreal dx = gradientVectorEnd.x() - gradientVectorStart.x();
626 qreal dy = gradientVectorEnd.y() - gradientVectorStart.y();
627 qreal distanceInPixels = sqrt(dx * dx + dy * dy);
628 // Compute the area to be be smoothed
629 // based on the length of the gradient
630 m_antiAliasThresholdNormalized = antiAliasThreshold / distanceInPixels;
631 m_antiAliasThresholdNormalizedRev = 1. - m_antiAliasThresholdNormalized;
632 m_antiAliasThresholdNormalizedDbl = 2. * m_antiAliasThresholdNormalized;
633
634 m_shapeStrategy = shapeStrategy;
635 m_repeatStrategy = repeatStrategy;
636
637 m_reverseGradient = reverseGradient;
638
639 m_cachedGradient = cachedGradient;
640 m_extremeColors[0] = m_cachedGradient->cachedAt(1.);
641 m_extremeColors[1] = m_cachedGradient->cachedAt(0.);
642
643 m_colorSpace = m_cachedGradient->colorSpace();
644
645 m_resultColor = QVector<quint8>(m_colorSpace->pixelSize());
646}
647
648const quint8 *RepeatForwardsPaintPolicy::colorAt(qreal x, qreal y) const
649{
650 qreal t = m_shapeStrategy->valueAt(x, y);
651 // Early return if the pixel is near the center of the gradient if
652 // the shape is radial or square.
653 // This prevents applying smoothing since there are
654 // no aliasing artifacts in these gradient shapes at the center
655 if (t <= m_antiAliasThresholdNormalized &&
659 if (m_reverseGradient) {
660 t = 1 - t;
661 }
662 return m_cachedGradient->cachedAt(t);
663 }
664
665 t = m_repeatStrategy->valueAt(t);
666
667 if (m_reverseGradient) {
668 t = 1 - t;
669 }
670
671 // If this pixel is in the area of the smoothing,
672 // then perform bilinear interpolation between the extreme colors.
673 if (t <= m_antiAliasThresholdNormalized || t >= m_antiAliasThresholdNormalizedRev) {
674 qreal s;
675 if (t <= m_antiAliasThresholdNormalized) {
676 s = .5 + t / m_antiAliasThresholdNormalizedDbl;
677 } else {
678 s = (t - m_antiAliasThresholdNormalizedRev) / m_antiAliasThresholdNormalizedDbl;
679 }
680
681 qint16 colorWeights[2];
682 colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
683 colorWeights[1] = qint16_MAX - colorWeights[0];
684
685 m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
686
687 return m_resultColor.data();
688 }
689
690 return m_cachedGradient->cachedAt(t);
691}
692
693class ConicalGradientPaintPolicy
694{
695public:
696 void setup(const QPointF& gradientVectorStart,
697 const QPointF& gradientVectorEnd,
698 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
699 const GradientRepeatStrategy *repeatStrategy,
700 qreal antiAliasThreshold,
701 bool reverseGradient,
702 const KoCachedGradient * cachedGradient);
703
704 const quint8 *colorAt(qreal x, qreal y) const;
705
706private:
707 QPointF m_gradientVectorStart;
709 const GradientRepeatStrategy *m_repeatStrategy;
710 qreal m_singularityThreshold;
711 qreal m_antiAliasThreshold;
712 bool m_reverseGradient;
713 const KoCachedGradient *m_cachedGradient;
714 const quint8 *m_extremeColors[2];
715 const KoColorSpace *m_colorSpace;
716 mutable QVector<quint8> m_resultColor;
717};
718
719void ConicalGradientPaintPolicy::setup(const QPointF& gradientVectorStart,
720 const QPointF& gradientVectorEnd,
721 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
722 const GradientRepeatStrategy *repeatStrategy,
723 qreal antiAliasThreshold,
724 bool reverseGradient,
725 const KoCachedGradient * cachedGradient)
726{
727 Q_UNUSED(gradientVectorEnd);
728
729 m_gradientVectorStart = gradientVectorStart;
730
731 m_shapeStrategy = shapeStrategy;
732 m_repeatStrategy = repeatStrategy;
733
734 m_singularityThreshold = 8.;
735 m_antiAliasThreshold = antiAliasThreshold;
736
737 m_reverseGradient = reverseGradient;
738
739 m_cachedGradient = cachedGradient;
740 m_extremeColors[0] = m_cachedGradient->cachedAt(1.);
741 m_extremeColors[1] = m_cachedGradient->cachedAt(0.);
742
743 m_colorSpace = m_cachedGradient->colorSpace();
744
745 m_resultColor = QVector<quint8>(m_colorSpace->pixelSize());
746}
747
748const quint8 *ConicalGradientPaintPolicy::colorAt(qreal x, qreal y) const
749{
750 // Compute the distance from the center of the gradient to the current pixel
751 qreal dx = x - m_gradientVectorStart.x();
752 qreal dy = y - m_gradientVectorStart.y();
753 qreal distanceInPixels = sqrt(dx * dx + dy * dy);
754 // Compute the perimeter for this distance
755 qreal perimeter = 2. * M_PI * distanceInPixels;
756 // The smoothing is applied in the vicinity of the aliased border.
757 // The width of the vicinity is an area antiAliasThreshold pixels wide
758 // to each side of the border, but in this case the area is scaled down
759 // if it is too close to the center
760 qreal antiAliasThresholdNormalized;
761 if (distanceInPixels < m_singularityThreshold){
762 antiAliasThresholdNormalized = distanceInPixels * m_antiAliasThreshold / m_singularityThreshold;
763 } else {
764 antiAliasThresholdNormalized = m_antiAliasThreshold;
765 }
766 antiAliasThresholdNormalized = antiAliasThresholdNormalized / perimeter;
767 qreal antiAliasThresholdNormalizedRev = 1. - antiAliasThresholdNormalized;
768 qreal antiAliasThresholdNormalizedDbl = 2. * antiAliasThresholdNormalized;
769
770 qreal t = m_shapeStrategy->valueAt(x, y);
771 t = m_repeatStrategy->valueAt(t);
772
773 if (m_reverseGradient) {
774 t = 1 - t;
775 }
776
777 // If this pixel is in the area of the smoothing,
778 // then perform bilinear interpolation between the extreme colors.
779 if (t <= antiAliasThresholdNormalized || t >= antiAliasThresholdNormalizedRev) {
780 qreal s;
781 if (t <= antiAliasThresholdNormalized) {
782 s = .5 + t / antiAliasThresholdNormalizedDbl;
783 } else {
784 s = (t - antiAliasThresholdNormalizedRev) / antiAliasThresholdNormalizedDbl;
785 }
786
787 qint16 colorWeights[2];
788 colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
789 colorWeights[1] = qint16_MAX - colorWeights[0];
790
791 m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
792
793 return m_resultColor.data();
794 }
795
796 return m_cachedGradient->cachedAt(t);
797}
798
799class SpyralGradientRepeatNonePaintPolicy
800{
801public:
802 SpyralGradientRepeatNonePaintPolicy(bool isReverseSpiral = false);
803
804 void setup(const QPointF& gradientVectorStart,
805 const QPointF& gradientVectorEnd,
806 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
807 const GradientRepeatStrategy *repeatStrategy,
808 qreal antiAliasThreshold,
809 bool reverseGradient,
810 const KoCachedGradient * cachedGradient);
811
812 const quint8 *colorAt(qreal x, qreal y) const;
813
814private:
815 QPointF m_gradientVectorStart;
816 qreal m_distanceInPixels {0};
817 qreal m_singularityThreshold {0};
818 qreal m_angle {0};
820 const GradientRepeatStrategy *m_repeatStrategy {0};
821 qreal m_antiAliasThreshold {0};
822 bool m_reverseGradient {false};
823 const KoCachedGradient *m_cachedGradient {0};
824 mutable const quint8 *m_extremeColors[2];
825 const KoColorSpace *m_colorSpace {0};
826 mutable QVector<quint8> m_resultColor;
827 bool m_isReverseSpiral {false};
828};
829
830SpyralGradientRepeatNonePaintPolicy::SpyralGradientRepeatNonePaintPolicy(bool isReverseSpiral)
831 : m_isReverseSpiral(isReverseSpiral)
832{
833}
834
835void SpyralGradientRepeatNonePaintPolicy::setup(const QPointF& gradientVectorStart,
836 const QPointF& gradientVectorEnd,
837 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
838 const GradientRepeatStrategy *repeatStrategy,
839 qreal antiAliasThreshold,
840 bool reverseGradient,
841 const KoCachedGradient * cachedGradient)
842{
843 m_gradientVectorStart = gradientVectorStart;
844
845 qreal dx = gradientVectorEnd.x() - gradientVectorStart.x();
846 qreal dy = gradientVectorEnd.y() - gradientVectorStart.y();
847 m_distanceInPixels = sqrt(dx * dx + dy * dy);
848 m_singularityThreshold = m_distanceInPixels / 32.;
849 m_angle = atan2(dy, dx) + M_PI;
850
851 m_shapeStrategy = shapeStrategy;
852 m_repeatStrategy = repeatStrategy;
853
854 m_antiAliasThreshold = antiAliasThreshold;
855
856 m_reverseGradient = reverseGradient;
857
858 m_cachedGradient = cachedGradient;
859
860 m_colorSpace = m_cachedGradient->colorSpace();
861
862 m_resultColor = QVector<quint8>(m_colorSpace->pixelSize());
863}
864
865const quint8 *SpyralGradientRepeatNonePaintPolicy::colorAt(qreal x, qreal y) const
866{
867 // Compute the distance from the center of the gradient to thecurrent pixel
868 qreal dx = x - m_gradientVectorStart.x();
869 qreal dy = y - m_gradientVectorStart.y();
870 qreal distanceInPixels = sqrt(dx * dx + dy * dy);
871 // Compute the perimeter for this distance
872 qreal perimeter = 2. * M_PI * distanceInPixels;
873 // The smoothing is applied in the vicinity of the aliased border.
874 // The width of the vicinity is an area antiAliasThreshold pixels wide
875 // to each side of the border, but in this case the area is scaled down
876 // if it is too close to the center
877 qreal antiAliasThresholdNormalized;
878 if (distanceInPixels < m_singularityThreshold) {
879 antiAliasThresholdNormalized = distanceInPixels * m_antiAliasThreshold / m_singularityThreshold;
880 } else {
881 antiAliasThresholdNormalized = m_antiAliasThreshold;
882 }
883 antiAliasThresholdNormalized = antiAliasThresholdNormalized / perimeter;
884 qreal antiAliasThresholdNormalizedRev = 1. - antiAliasThresholdNormalized;
885 qreal antiAliasThresholdNormalizedDbl = 2. * antiAliasThresholdNormalized;
886
887 qreal t = m_shapeStrategy->valueAt(x, y);
888 t = m_repeatStrategy->valueAt(t);
889
890 if (m_reverseGradient) {
891 t = 1 - t;
892 }
893
894 // Compute the area to be be smoothed based on the angle of the gradient
895 // and the angle of the current pixel to the center of the gradient
896 qreal angle = atan2(dy, dx) + M_PI;
897 angle -= m_angle;
898 if (angle < 0.) {
899 angle += 2. * M_PI;
900 }
901 angle /= (2. * M_PI);
902
903 angle = m_repeatStrategy->valueAt(angle);
904
905 // If this pixel is in the area of the smoothing,
906 // then perform bilinear interpolation between the extreme colors.
907 if (distanceInPixels < m_distanceInPixels && (angle <= antiAliasThresholdNormalized || angle >= antiAliasThresholdNormalizedRev)) {
908 qreal s;
909 if (angle <= antiAliasThresholdNormalized) {
910 s = .5 + angle / antiAliasThresholdNormalizedDbl;
911 } else {
912 s = (angle - antiAliasThresholdNormalizedRev) / antiAliasThresholdNormalizedDbl;
913 }
914
915 if (m_reverseGradient) {
916 distanceInPixels = m_distanceInPixels - distanceInPixels;
917 m_extremeColors[0] = m_cachedGradient->cachedAt(0.);
918 } else {
919 m_extremeColors[0] = m_cachedGradient->cachedAt(1.);
920 }
921
922 if (m_isReverseSpiral) {
923 m_extremeColors[1] = m_extremeColors[0];
924 m_extremeColors[0] = (m_cachedGradient->cachedAt(distanceInPixels / m_distanceInPixels));
925 } else {
926 m_extremeColors[1] = (m_cachedGradient->cachedAt(distanceInPixels / m_distanceInPixels));
927 }
928
929 qint16 colorWeights[2];
930 colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
931 colorWeights[1] = qint16_MAX - colorWeights[0];
932
933 m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
934
935 return m_resultColor.data();
936 }
937
938 return m_cachedGradient->cachedAt(t);
939}
940
941class NoAntialiasPaintPolicy
942{
943public:
944 void setup(const QPointF& gradientVectorStart,
945 const QPointF& gradientVectorEnd,
946 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
947 const GradientRepeatStrategy *repeatStrategy,
948 qreal antiAliasThreshold,
949 bool reverseGradient,
950 const KoCachedGradient * cachedGradient);
951
952 const quint8 *colorAt(qreal x, qreal y) const;
953
954private:
956 const GradientRepeatStrategy *m_repeatStrategy {0};
957 bool m_reverseGradient {false};
958 const KoCachedGradient *m_cachedGradient {0};
959};
960
961void NoAntialiasPaintPolicy::setup(const QPointF& gradientVectorStart,
962 const QPointF& gradientVectorEnd,
963 const QSharedPointer<KisGradientShapeStrategy> &shapeStrategy,
964 const GradientRepeatStrategy *repeatStrategy,
965 qreal antiAliasThreshold,
966 bool reverseGradient,
967 const KoCachedGradient * cachedGradient)
968{
969 Q_UNUSED(gradientVectorStart);
970 Q_UNUSED(gradientVectorEnd);
971 Q_UNUSED(antiAliasThreshold);
972 m_shapeStrategy = shapeStrategy;
973 m_repeatStrategy = repeatStrategy;
974 m_reverseGradient = reverseGradient;
975 m_cachedGradient = cachedGradient;
976}
977
978const quint8 *NoAntialiasPaintPolicy::colorAt(qreal x, qreal y) const
979{
980 qreal t = m_shapeStrategy->valueAt(x, y);
981 t = m_repeatStrategy->valueAt(t);
982
983 if (m_reverseGradient) {
984 t = 1 - t;
985 }
986
987 return m_cachedGradient->cachedAt(t);
988}
989
990}
991
992struct Q_DECL_HIDDEN KisGradientPainter::Private
993{
995
999 const QRect &_processRect)
1000 : precalculatedShapeStrategy(_precalculatedShapeStrategy),
1001 processRect(_processRect) {}
1002
1005 };
1006
1008};
1009
1011 : m_d(new Private())
1012{
1013}
1014
1016 : KisPainter(device),
1017 m_d(new Private())
1018{
1019}
1020
1022 : KisPainter(device, selection),
1023 m_d(new Private())
1024{
1025}
1026
1030
1035
1036KisGradientShapeStrategy* 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
1056{
1057 if (!m_d->processRegions.isEmpty()) return;
1058
1059 QPainterPath path;
1060
1061 if (selection()) {
1062 if (!selection()->outlineCacheValid()) {
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
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
1089bool 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
1109bool 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 smoothing 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 ||
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
1174template <class T>
1175bool 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
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 }
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 }
1246 Private::ProcessRegion r(toQShared(new ReverseSpiralGradientStrategy(gradientVectorStart, gradientVectorEnd)),
1247 requestedRect);
1248 m_d->processRegions.clear();
1249 m_d->processRegions << r;
1250 break;
1251 }
1254 repeat = GradientRepeatNone;
1255 break;
1256 }
1257
1258 GradientRepeatStrategy *repeatStrategy = 0;
1259
1260 switch (repeat) {
1261 case GradientRepeatNone:
1262 repeatStrategy = GradientRepeatNoneStrategy::instance();
1263 break;
1265 repeatStrategy = GradientRepeatForwardsStrategy::instance();
1266 break;
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
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
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
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
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}
float value(const T *src, size_t ch)
@ DITHER_NONE
Definition KisDitherOp.h:22
@ DITHER_BEST
Definition KisDitherOp.h:24
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
qreal distance(const QPointF &p1, const QPointF &p2)
PythonPluginManager * instance
virtual quint8 * rawData()=0
virtual const quint8 * rawDataConst() const =0
virtual void dither(const quint8 *src, quint8 *dst, int x, int y) const =0
KisPaintDeviceSP createCompositionSourceDevice() const
const KoColorSpace * colorSpace() const
KisDefaultBoundsBaseSP defaultBounds() const
KisRandomAccessorSP createRandomAccessorNG()
KoUpdater * progressUpdater
KisSelectionSP selection
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
KisPaintDeviceSP device
KoAbstractGradientSP gradient
virtual qint32 rowStride(qint32 x, qint32 y) const =0
virtual qint32 numContiguousRows(qint32 y) const =0
virtual void moveTo(qint32 x, qint32 y)=0
virtual qint32 numContiguousColumns(qint32 x) const =0
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE int x() const
ALWAYS_INLINE int y() const
const quint8 * cachedAt(qreal t) const
gets the color data at position 0 <= t <= 1
const KoColorSpace * colorSpace() const
virtual quint32 pixelSize() const =0
virtual const KisDitherOp * ditherOp(const QString &depth, DitherType type) const
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
KoMixColorsOp * mixColorsOp
virtual const KoColorProfile * profile() const =0
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
virtual void mixColors(const quint8 *const *colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum=255) const =0
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define bounds(x, a, b)
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
const qint16 qint16_MAX
Definition kis_global.h:28
#define M_PI
Definition kis_global.h:111
KisGradientShapeStrategy * createPolygonShapeStrategy(const QPainterPath &path, const QRect &boundingRect)
QSharedPointer< T > toQShared(T *ptr)
KRITAIMAGE_EXPORT qreal atan2(qreal y, qreal x)
atan2 replacement
KRITAFLAKE_EXPORT QColor colorAt(qreal position, const QGradientStops &stops)
Calculates color at given position from given gradient stops.
qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue)
QList< QPainterPath > splitDisjointPaths(const QPainterPath &path)
QSharedPointer< KisGradientShapeStrategy > precalculatedShapeStrategy
ProcessRegion(QSharedPointer< KisGradientShapeStrategy > _precalculatedShapeStrategy, const QRect &_processRect)
bool paintGradient(const QPointF &gradientVectorStart, const QPointF &gradientVectorEnd, enumGradientRepeat repeat, double antiAliasThreshold, bool reverseGradient, qint32 startx, qint32 starty, qint32 width, qint32 height, bool useDithering=false)
void setGradientShape(enumGradientShape shape)
const QScopedPointer< Private > m_d
QVector< ProcessRegion > processRegions
void recalculateOutlineCache()
QRect selectedExactRect() const
Slow, but exact way of determining the rectangle that encloses the selection.
QPainterPath outlineCache() const
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()