QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgscolorrampimpl.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscolorrampimpl.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7***************************************************************************
8* *
9* This program is free software; you can redistribute it and/or modify *
10* it under the terms of the GNU General Public License as published by *
11* the Free Software Foundation; either version 2 of the License, or *
12* (at your option) any later version. *
13* *
14***************************************************************************/
15
16#include "qgscolorrampimpl.h"
17
18#include <algorithm>
19#include <random>
20
22#include "qgscolorutils.h"
23#include "qgscptcityarchive.h"
24#include "qgslogger.h"
25
26#include <QDir>
27#include <QFileInfo>
28#include <QString>
29#include <QTime>
30
31using namespace Qt::StringLiterals;
32
34
35
36static QColor _interpolateRgb( const QColor &c1, const QColor &c2, const double value, const Qgis::AngularDirection )
37{
38 if ( std::isnan( value ) )
39 return c2;
40
41 const qreal red1 = c1.redF();
42 const qreal red2 = c2.redF();
43 const qreal red = ( red1 + value * ( red2 - red1 ) );
44
45 const qreal green1 = c1.greenF();
46 const qreal green2 = c2.greenF();
47 const qreal green = ( green1 + value * ( green2 - green1 ) );
48
49 const qreal blue1 = c1.blueF();
50 const qreal blue2 = c2.blueF();
51 const qreal blue = ( blue1 + value * ( blue2 - blue1 ) );
52
53 const qreal alpha1 = c1.alphaF();
54 const qreal alpha2 = c2.alphaF();
55 const qreal alpha = ( alpha1 + value * ( alpha2 - alpha1 ) );
56
57 return QColor::fromRgbF( red, green, blue, alpha );
58}
59
60static QColor _interpolateHsv( const QColor &c1, const QColor &c2, const double value, const Qgis::AngularDirection direction )
61{
62 if ( std::isnan( value ) )
63 return c2;
64
65 qreal hue1 = c1.hsvHueF();
66 qreal hue2 = c2.hsvHueF();
67 qreal hue = 0;
68 if ( hue1 == -1 )
69 hue = hue2;
70 else if ( hue2 == -1 )
71 hue = hue1;
72 else
73 {
74 switch ( direction )
75 {
77 {
78 if ( hue1 < hue2 )
79 hue1 += 1;
80
81 hue = hue1 - value * ( hue1 - hue2 );
82 if ( hue < 0 )
83 hue += 1;
84 if ( hue > 1 )
85 hue -= 1;
86 break;
87 }
88
90 {
91 if ( hue2 < hue1 )
92 hue2 += 1;
93
94 hue = hue1 + value * ( hue2 - hue1 );
95 if ( hue > 1 )
96 hue -= 1;
97 break;
98 }
100 break;
101 }
102 }
103
104 const qreal saturation1 = c1.hsvSaturationF();
105 const qreal saturation2 = c2.hsvSaturationF();
106 const qreal saturation = ( saturation1 + value * ( saturation2 - saturation1 ) );
107
108 const qreal value1 = c1.valueF();
109 const qreal value2 = c2.valueF();
110 const qreal valueOut = ( value1 + value * ( value2 - value1 ) );
111
112 const qreal alpha1 = c1.alphaF();
113 const qreal alpha2 = c2.alphaF();
114 const qreal alpha = ( alpha1 + value * ( alpha2 - alpha1 ) );
115
116 return QColor::fromHsvF( hue > 1 ? hue - 1 : hue, saturation, valueOut, alpha );
117}
118
119static QColor _interpolateHsl( const QColor &c1, const QColor &c2, const double value, const Qgis::AngularDirection direction )
120{
121 if ( std::isnan( value ) )
122 return c2;
123
124 qreal hue1 = c1.hslHueF();
125 qreal hue2 = c2.hslHueF();
126 qreal hue = 0;
127 if ( hue1 == -1 )
128 hue = hue2;
129 else if ( hue2 == -1 )
130 hue = hue1;
131 else
132 {
133 switch ( direction )
134 {
136 {
137 if ( hue1 < hue2 )
138 hue1 += 1;
139
140 hue = hue1 - value * ( hue1 - hue2 );
141 if ( hue < 0 )
142 hue += 1;
143 if ( hue > 1 )
144 hue -= 1;
145 break;
146 }
147
149 {
150 if ( hue2 < hue1 )
151 hue2 += 1;
152
153 hue = hue1 + value * ( hue2 - hue1 );
154 if ( hue > 1 )
155 hue -= 1;
156 break;
157 }
159 break;
160 }
161 }
162
163 const qreal saturation1 = c1.hslSaturationF();
164 const qreal saturation2 = c2.hslSaturationF();
165 const qreal saturation = ( saturation1 + value * ( saturation2 - saturation1 ) );
166
167 const qreal lightness1 = c1.lightnessF();
168 const qreal lightness2 = c2.lightnessF();
169 const qreal lightness = ( lightness1 + value * ( lightness2 - lightness1 ) );
170
171 const qreal alpha1 = c1.alphaF();
172 const qreal alpha2 = c2.alphaF();
173 const qreal alpha = ( alpha1 + value * ( alpha2 - alpha1 ) );
174
175 return QColor::fromHslF( hue > 1 ? hue - 1 : hue, saturation, lightness, alpha );
176}
177
178static QColor interpolateCmyk( const QColor &c1, const QColor &c2, const double value, const Qgis::AngularDirection )
179{
180 if ( std::isnan( value ) )
181 return c2;
182
183 const qreal cyan1 = c1.cyanF();
184 const qreal cyan2 = c2.cyanF();
185 const qreal cyan = ( cyan1 + value * ( cyan2 - cyan1 ) );
186
187 const qreal magenta1 = c1.magentaF();
188 const qreal magenta2 = c2.magentaF();
189 const qreal magenta = ( magenta1 + value * ( magenta2 - magenta1 ) );
190
191 const qreal yellow1 = c1.yellowF();
192 const qreal yellow2 = c2.yellowF();
193 const qreal yellow = ( yellow1 + value * ( yellow2 - yellow1 ) );
194
195 const qreal black1 = c1.blackF();
196 const qreal black2 = c2.blackF();
197 const qreal black = ( black1 + value * ( black2 - black1 ) );
198
199 const qreal alpha1 = c1.alphaF();
200 const qreal alpha2 = c2.alphaF();
201 const qreal alpha = ( alpha1 + value * ( alpha2 - alpha1 ) );
202
203 return QColor::fromCmykF( cyan, magenta, yellow, black, alpha ); // NOLINT(bugprone-narrowing-conversions): TODO QGIS 5 remove the nolint instructions, QColor was qreal (double) and is now float
204}
205
207
208
210 : offset( offset )
211 , color( color )
212 , mFunc( _interpolateRgb )
213{
214
215}
216
217void QgsGradientStop::setColorSpec( QColor::Spec spec )
218{
219 mColorSpec = spec;
220
221 switch ( mColorSpec )
222 {
223 case QColor::Rgb:
224 case QColor::Invalid:
225 case QColor::ExtendedRgb:
226 mFunc = _interpolateRgb;
227 break;
228 case QColor::Cmyk:
229 mFunc = interpolateCmyk;
230 break;
231 case QColor::Hsv:
232 mFunc = _interpolateHsv;
233 break;
234 case QColor::Hsl:
235 mFunc = _interpolateHsl;
236 break;
237 }
238}
239
241 bool discrete, const QgsGradientStopsList &stops )
242 : mColor1( color1 )
243 , mColor2( color2 )
244 , mDiscrete( discrete )
245 , mStops( stops )
246 , mFunc( _interpolateRgb )
247{
248}
249
250QgsColorRamp *QgsGradientColorRamp::create( const QVariantMap &props )
251{
252 // color1 and color2
255 if ( props.contains( u"color1"_s ) )
256 color1 = QgsColorUtils::colorFromString( props[u"color1"_s].toString() );
257 if ( props.contains( u"color2"_s ) )
258 color2 = QgsColorUtils::colorFromString( props[u"color2"_s].toString() );
259
260 //stops
262 if ( props.contains( u"stops"_s ) )
263 {
264 const thread_local QRegularExpression rx( u"(?<!,rgb)(?<!,cmyk)(?<!,hsl)(?<!,hsv):"_s );
265 const auto constSplit = props[u"stops"_s].toString().split( rx );
266 for ( const QString &stop : constSplit )
267 {
268 const QStringList parts = stop.split( ';' );
269 if ( parts.size() != 2 && parts.size() != 4 )
270 continue;
271
272 QColor c = QgsColorUtils::colorFromString( parts.at( 1 ) );
273 stops.append( QgsGradientStop( parts.at( 0 ).toDouble(), c ) );
274
275 if ( parts.size() == 4 )
276 {
277 if ( parts.at( 2 ).compare( "rgb"_L1 ) == 0 )
278 stops.last().setColorSpec( QColor::Spec::Rgb );
279 else if ( parts.at( 2 ).compare( "hsv"_L1 ) == 0 )
280 stops.last().setColorSpec( QColor::Spec::Hsv );
281 else if ( parts.at( 2 ).compare( "hsl"_L1 ) == 0 )
282 stops.last().setColorSpec( QColor::Spec::Hsl );
283
284 if ( parts.at( 3 ).compare( "cw"_L1 ) == 0 )
285 stops.last().setDirection( Qgis::AngularDirection::Clockwise );
286 else if ( parts.at( 3 ).compare( "ccw"_L1 ) == 0 )
287 stops.last().setDirection( Qgis::AngularDirection::CounterClockwise );
288 }
289 }
290 }
291
292 // discrete vs. continuous
293 bool discrete = false;
294 if ( props.contains( u"discrete"_s ) )
295 {
296 if ( props[u"discrete"_s] == "1"_L1 )
297 discrete = true;
298 }
299
300 // search for information keys starting with "info_"
302 for ( QVariantMap::const_iterator it = props.constBegin();
303 it != props.constEnd(); ++it )
304 {
305 if ( it.key().startsWith( "info_"_L1 ) )
306 info[ it.key().mid( 5 )] = it.value().toString();
307 }
308
310 r->setInfo( info );
311
312 if ( props.contains( u"spec"_s ) )
313 {
314 const QString spec = props.value( u"spec"_s ).toString().trimmed();
315 if ( spec.compare( "rgb"_L1 ) == 0 )
316 r->setColorSpec( QColor::Spec::Rgb );
317 else if ( spec.compare( "hsv"_L1 ) == 0 )
318 r->setColorSpec( QColor::Spec::Hsv );
319 else if ( spec.compare( "hsl"_L1 ) == 0 )
320 r->setColorSpec( QColor::Spec::Hsl );
321 }
322
323 if ( props.contains( u"direction"_s ) )
324 {
325 const QString direction = props.value( u"direction"_s ).toString().trimmed();
326 if ( direction.compare( "ccw"_L1 ) == 0 )
328 else if ( direction.compare( "cw"_L1 ) == 0 )
330 }
331
332 return r;
333}
334
335double QgsGradientColorRamp::value( int index ) const
336{
337 if ( index <= 0 )
338 {
339 return 0;
340 }
341 else if ( index >= mStops.size() + 1 )
342 {
343 return 1;
344 }
345 else
346 {
347 return mStops[index - 1].offset;
348 }
349}
350
351QColor QgsGradientColorRamp::color( double value ) const
352{
353 if ( qgsDoubleNear( value, 0.0 ) || value < 0.0 )
354 {
355 return mColor1;
356 }
357 else if ( qgsDoubleNear( value, 1.0 ) || value > 1.0 )
358 {
359 return mColor2;
360 }
361 else if ( mStops.isEmpty() )
362 {
363 if ( mDiscrete )
364 return mColor1;
365
366 return mFunc( mColor1, mColor2, value, mDirection );
367 }
368 else
369 {
370 double lower = 0, upper = 0;
371 QColor c1 = mColor1, c2;
372 for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
373 {
374 if ( it->offset > value )
375 {
376 if ( mDiscrete )
377 return c1;
378
379 upper = it->offset;
380 c2 = it->color;
381
382 return qgsDoubleNear( upper, lower ) ? c1 : it->mFunc( c1, c2, ( value - lower ) / ( upper - lower ), it->mDirection );
383 }
384 lower = it->offset;
385 c1 = it->color;
386 }
387
388 if ( mDiscrete )
389 return c1;
390
391 upper = 1;
392 c2 = mColor2;
393 return qgsDoubleNear( upper, lower ) ? c1 : mFunc( c1, c2, ( value - lower ) / ( upper - lower ), mDirection );
394 }
395}
396
398{
400}
401
403{
404 QgsGradientStopsList newStops;
405 newStops.reserve( mStops.size() );
406
407 if ( mDiscrete )
408 {
410 mColor1 = mStops.at( mStops.size() - 1 ).color;
411 for ( int k = mStops.size() - 1; k >= 1; k-- )
412 {
413 newStops << QgsGradientStop( 1 - mStops.at( k ).offset, mStops.at( k - 1 ).color );
414 }
415 newStops << QgsGradientStop( 1 - mStops.at( 0 ).offset, mColor2 );
416 }
417 else
418 {
419 QColor tmpColor = mColor2;
421 mColor1 = tmpColor;
422 for ( int k = mStops.size() - 1; k >= 0; k-- )
423 {
424 newStops << QgsGradientStop( 1 - mStops.at( k ).offset, mStops.at( k ).color );
425 }
426 }
427
428 // transfer color spec, invert directions
429 if ( mStops.empty() )
430 {
431 // reverse direction
433 }
434 else
435 {
436 newStops[0].setColorSpec( mColorSpec );
438 for ( int i = 1, j = mStops.size() - 1; i < mStops.size(); ++i, --j )
439 {
440 newStops[i].setColorSpec( mStops.at( j ).colorSpec() );
442 }
443 mColorSpec = mStops.at( 0 ).colorSpec();
445 }
446
447 mStops = newStops;
448}
449
459
461{
462 QVariantMap map;
463 map[u"color1"_s] = QgsColorUtils::colorToString( mColor1 );
464 map[u"color2"_s] = QgsColorUtils::colorToString( mColor2 );
465 if ( !mStops.isEmpty() )
466 {
467 QStringList lst;
468 lst.reserve( mStops.size() );
469 for ( const QgsGradientStop &stop : mStops )
470 {
471 lst.append( u"%1;%2;%3;%4"_s.arg( stop.offset ).arg( QgsColorUtils::colorToString( stop.color ),
472 stop.colorSpec() == QColor::Rgb ? u"rgb"_s
473 : stop.colorSpec() == QColor::Hsv ? u"hsv"_s
474 : stop.colorSpec() == QColor::Hsl ? u"hsl"_s : QString(),
475 stop.direction() == Qgis::AngularDirection::CounterClockwise ? u"ccw"_s : u"cw"_s ) );
476 }
477 map[u"stops"_s] = lst.join( ':'_L1 );
478 }
479
480 map[u"discrete"_s] = mDiscrete ? "1" : "0";
481
482 for ( QgsStringMap::const_iterator it = mInfo.constBegin();
483 it != mInfo.constEnd(); ++it )
484 {
485 map["info_" + it.key()] = it.value();
486 }
487
488 switch ( mColorSpec )
489 {
490 case QColor::Rgb:
491 map[u"spec"_s ] = u"rgb"_s;
492 break;
493 case QColor::Hsv:
494 map[u"spec"_s ] = u"hsv"_s;
495 break;
496 case QColor::Hsl:
497 map[u"spec"_s ] = u"hsl"_s;
498 break;
499 case QColor::Cmyk:
500 case QColor::Invalid:
501 case QColor::ExtendedRgb:
502 break;
503 }
504
505 switch ( mDirection )
506 {
508 map[u"direction"_s ] = u"cw"_s;
509 break;
511 map[u"direction"_s ] = u"ccw"_s;
512 break;
514 break;
515 }
516
517 map[u"rampType"_s] = type();
518 return map;
519}
521{
522 if ( discrete == mDiscrete )
523 return;
524
525 // if going to/from Discrete, re-arrange stops
526 // this will only work when stops are equally-spaced
527 QgsGradientStopsList newStops;
528 if ( discrete )
529 {
530 // re-arrange stops offset
531 int numStops = mStops.count() + 2;
532 int i = 1;
533 for ( QgsGradientStopsList::const_iterator it = mStops.constBegin();
534 it != mStops.constEnd(); ++it )
535 {
536 newStops.append( QgsGradientStop( static_cast< double >( i ) / numStops, it->color ) );
537 if ( i == numStops - 1 )
538 break;
539 i++;
540 }
541 // replicate last color
542 newStops.append( QgsGradientStop( static_cast< double >( i ) / numStops, mColor2 ) );
543 }
544 else
545 {
546 // re-arrange stops offset, remove duplicate last color
547 int numStops = mStops.count() + 2;
548 int i = 1;
549 for ( QgsGradientStopsList::const_iterator it = mStops.constBegin();
550 it != mStops.constEnd(); ++it )
551 {
552 newStops.append( QgsGradientStop( static_cast< double >( i ) / ( numStops - 2 ), it->color ) );
553 if ( i == numStops - 3 )
554 break;
555 i++;
556 }
557 }
558 mStops = newStops;
559 mDiscrete = discrete;
560}
561
562bool stopLessThan( const QgsGradientStop &s1, const QgsGradientStop &s2 )
563{
564 return s1.offset < s2.offset;
565}
566
568{
569 mStops = stops;
570
571 //sort stops by offset
572 std::sort( mStops.begin(), mStops.end(), stopLessThan );
573}
574
575void QgsGradientColorRamp::addStopsToGradient( QGradient *gradient, double opacity ) const
576{
577 //copy color ramp stops to a QGradient
578 QColor color1 = mColor1;
579 QColor color2 = mColor2;
580 if ( opacity < 1 )
581 {
582 color1.setAlpha( color1.alpha() * opacity );
583 color2.setAlpha( color2.alpha() * opacity );
584 }
585 gradient->setColorAt( 0, color1 );
586 gradient->setColorAt( 1, color2 );
587
588 double lastOffset = 0;
589 for ( const QgsGradientStop &stop : mStops )
590 {
591
592 QColor rampColor = stop.color;
593 if ( opacity < 1 )
594 {
595 rampColor.setAlpha( rampColor.alpha() * opacity );
596 }
597 gradient->setColorAt( stop.offset, rampColor );
598
599 if ( stop.colorSpec() != QColor::Rgb )
600 {
601 // QGradient only supports RGB interpolation. For other color specs we have
602 // to "fake" things by populating the gradient with additional stops
603 for ( double offset = lastOffset + 0.05; offset < stop.offset; offset += 0.05 )
604 {
605 QColor midColor = color( offset );
606 if ( opacity < 1 )
607 {
608 midColor.setAlpha( midColor.alpha() * opacity );
609 }
610 gradient->setColorAt( offset, midColor );
611 }
612 }
613 lastOffset = stop.offset;
614 }
615
616 if ( mColorSpec != QColor::Rgb )
617 {
618 for ( double offset = lastOffset + 0.05; offset < 1; offset += 0.05 )
619 {
620 QColor midColor = color( offset );
621 if ( opacity < 1 )
622 {
623 midColor.setAlpha( midColor.alpha() * opacity );
624 }
625 gradient->setColorAt( offset, midColor );
626 }
627 }
628}
629
630void QgsGradientColorRamp::setColorSpec( QColor::Spec spec )
631{
632 mColorSpec = spec;
633 switch ( mColorSpec )
634 {
635 case QColor::Rgb:
636 case QColor::Invalid:
637 case QColor::ExtendedRgb:
638 mFunc = _interpolateRgb;
639 break;
640 case QColor::Cmyk:
641 mFunc = interpolateCmyk;
642 break;
643 case QColor::Hsv:
644 mFunc = _interpolateHsv;
645 break;
646 case QColor::Hsl:
647 mFunc = _interpolateHsl;
648 break;
649 }
650}
651
652
654
655
665
667{
672
673 if ( props.contains( u"count"_s ) ) count = props[u"count"_s].toInt();
674 if ( props.contains( u"hueMin"_s ) ) hueMin = props[u"hueMin"_s].toInt();
675 if ( props.contains( u"hueMax"_s ) ) hueMax = props[u"hueMax"_s].toInt();
676 if ( props.contains( u"satMin"_s ) ) satMin = props[u"satMin"_s].toInt();
677 if ( props.contains( u"satMax"_s ) ) satMax = props[u"satMax"_s].toInt();
678 if ( props.contains( u"valMin"_s ) ) valMin = props[u"valMin"_s].toInt();
679 if ( props.contains( u"valMax"_s ) ) valMax = props[u"valMax"_s].toInt();
680
682}
683
684double QgsLimitedRandomColorRamp::value( int index ) const
685{
686 if ( mColors.empty() )
687 return 0;
688 return static_cast< double >( index ) / ( mColors.size() - 1 );
689}
690
692{
693 if ( value < 0 || value > 1 )
694 return QColor();
695
696 int colorCnt = mColors.count();
697 int colorIdx = std::min( static_cast< int >( value * colorCnt ), colorCnt - 1 );
698
699 if ( colorIdx >= 0 && colorIdx < colorCnt )
700 return mColors.at( colorIdx );
701
702 return QColor();
703}
704
709
714
716{
717 QVariantMap map;
718 map[u"count"_s] = QString::number( mCount );
719 map[u"hueMin"_s] = QString::number( mHueMin );
720 map[u"hueMax"_s] = QString::number( mHueMax );
721 map[u"satMin"_s] = QString::number( mSatMin );
722 map[u"satMax"_s] = QString::number( mSatMax );
723 map[u"valMin"_s] = QString::number( mValMin );
724 map[u"valMax"_s] = QString::number( mValMax );
725 map[u"rampType"_s] = type();
726 return map;
727}
728
730 int hueMax, int hueMin, int satMax, int satMin, int valMax, int valMin )
731{
732 int h, s, v;
733 QList<QColor> colors;
734
735 //normalize values
736 int safeHueMax = std::max( hueMin, hueMax );
737 int safeHueMin = std::min( hueMin, hueMax );
738 int safeSatMax = std::max( satMin, satMax );
739 int safeSatMin = std::min( satMin, satMax );
740 int safeValMax = std::max( valMin, valMax );
741 int safeValMin = std::min( valMin, valMax );
742
743 //start hue at random angle
744 double currentHueAngle = 360.0 * static_cast< double >( std::rand() ) / RAND_MAX;
745
746 colors.reserve( count );
747 for ( int i = 0; i < count; ++i )
748 {
749 //increment hue by golden ratio (approx 137.507 degrees)
750 //as this minimizes hue nearness as count increases
751 //see http://basecase.org/env/on-rainbows for more details
752 currentHueAngle += 137.50776;
753 //scale hue to between hueMax and hueMin
754 h = std::clamp( std::round( ( std::fmod( currentHueAngle, 360.0 ) / 360.0 ) * ( safeHueMax - safeHueMin ) + safeHueMin ), 0.0, 359.0 );
755 s = std::clamp( ( static_cast<int>( std::rand() ) % ( safeSatMax - safeSatMin + 1 ) ) + safeSatMin, 0, 255 );
756 v = std::clamp( ( static_cast<int>( std::rand() ) % ( safeValMax - safeValMin + 1 ) ) + safeValMin, 0, 255 );
757 colors.append( QColor::fromHsv( h, s, v ) );
758 }
759 return colors;
760}
761
766
768
770{
771 return -1;
772}
773
774double QgsRandomColorRamp::value( int index ) const
775{
776 Q_UNUSED( index )
777 return 0.0;
778}
779
780QColor QgsRandomColorRamp::color( double value ) const
781{
782 int minVal = 130;
783 int maxVal = 255;
784
785 //if value is nan, then use last precalculated color
786 if ( std::isnan( value ) )
787 {
788 value = 1.0;
789 }
790 // Caller has converted an index into a value in [0.0, 1.0]
791 // by doing "index / (mTotalColorCount - 1)"; retrieve the original index.
792 int colorIndex = std::round( value * ( mTotalColorCount - 1 ) );
793 if ( mTotalColorCount >= 1 && mPrecalculatedColors.length() > colorIndex )
794 {
795 //use precalculated hue
796 return mPrecalculatedColors.at( colorIndex );
797 }
798
799 //can't use precalculated hues, use a totally random hue
800 int h = static_cast< int >( 360.0 * std::rand() / ( RAND_MAX + 1.0 ) );
801 int s = ( std::rand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
802 int v = ( std::rand() % ( maxVal - minVal + 1 ) ) + minVal;
803 return QColor::fromHsv( h, s, v );
804}
805
806void QgsRandomColorRamp::setTotalColorCount( const int colorCount )
807{
808 //calculate colors in advance, so that we can ensure they are more visually distinct than pure random colors
809 mPrecalculatedColors.clear();
810 mTotalColorCount = colorCount;
811
812 //This works OK for low color counts, but for > 10 or so colors there's still a good chance of
813 //similar colors being picked. TODO - investigate alternative "n-visually distinct color" routines
814
815 //random offsets
816 double hueOffset = ( 360.0 * std::rand() / ( RAND_MAX + 1.0 ) );
817
818 //try to maximise difference between hues. this is not an ideal implementation, as constant steps
819 //through the hue wheel are not visually perceived as constant changes in hue
820 //(for instance, we are much more likely to get green hues than yellow hues)
821 double hueStep = 359.0 / colorCount;
822 double currentHue = hueOffset;
823
824 //build up a list of colors
825 for ( int idx = 0; idx < colorCount; ++ idx )
826 {
827 int h = static_cast< int >( std::round( currentHue ) ) % 360;
828 int s = ( std::rand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
829 int v = ( std::rand() % ( DEFAULT_RANDOM_VAL_MAX - DEFAULT_RANDOM_VAL_MIN + 1 ) ) + DEFAULT_RANDOM_VAL_MIN;
830 mPrecalculatedColors << QColor::fromHsv( h, s, v );
831 currentHue += hueStep;
832 }
833
834 //lastly, shuffle color list
835 std::random_device rd;
836 std::mt19937 g( rd() );
837 std::shuffle( mPrecalculatedColors.begin(), mPrecalculatedColors.end(), g );
838}
839
841{
843}
844
849
851{
852 return QVariantMap();
853}
854
856
859 , mColors( colors )
860 , mInverted( inverted )
861{
862 loadPalette();
863}
864
866{
869 bool inverted = false;
870
871 if ( props.contains( u"schemeName"_s ) )
872 schemeName = props[u"schemeName"_s].toString();
873 if ( props.contains( u"colors"_s ) )
874 colors = props[u"colors"_s].toInt();
875 if ( props.contains( u"inverted"_s ) )
876 inverted = props[u"inverted"_s].toInt();
877
878 return new QgsColorBrewerColorRamp( schemeName, colors, inverted );
879}
880
882{
884
885 if ( mInverted )
886 {
887 QList<QColor> tmpPalette;
888
889 for ( int k = mPalette.size() - 1; k >= 0; k-- )
890 {
891 tmpPalette << mPalette.at( k );
892 }
893 mPalette = tmpPalette;
894 }
895}
896
901
906
907double QgsColorBrewerColorRamp::value( int index ) const
908{
909 if ( mPalette.empty() )
910 return 0;
911 return static_cast< double >( index ) / ( mPalette.size() - 1 );
912}
913
915{
916 if ( mPalette.isEmpty() || value < 0 || value > 1 || std::isnan( value ) )
917 return QColor();
918
919 int paletteEntry = static_cast< int >( value * mPalette.count() );
920 if ( paletteEntry >= mPalette.count() )
921 paletteEntry = mPalette.count() - 1;
922 return mPalette.at( paletteEntry );
923}
924
930
935
937{
938 QVariantMap map;
939 map[u"schemeName"_s] = mSchemeName;
940 map[u"colors"_s] = QString::number( mColors );
941 map[u"inverted"_s] = QString::number( mInverted );
942 map[u"rampType"_s] = type();
943 return map;
944}
945
946
948
949
951 bool inverted, bool doLoadFile )
955 , mInverted( inverted )
956{
957 // TODO replace this with hard-coded data in the default case
958 // don't load file if variant is missing
959 if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
960 loadFile();
961}
962
964 const QString &variantName, bool inverted, bool doLoadFile )
969 , mInverted( inverted )
970{
972
973 // TODO replace this with hard-coded data in the default case
974 // don't load file if variant is missing
975 if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
976 loadFile();
977}
978
979QgsColorRamp *QgsCptCityColorRamp::create( const QVariantMap &props ) // cppcheck-suppress duplInheritedMember
980{
983 bool inverted = false;
984
985 if ( props.contains( u"schemeName"_s ) )
986 schemeName = props[u"schemeName"_s].toString();
987 if ( props.contains( u"variantName"_s ) )
988 variantName = props[u"variantName"_s].toString();
989 if ( props.contains( u"inverted"_s ) )
990 inverted = props[u"inverted"_s].toInt();
991
992 return new QgsCptCityColorRamp( schemeName, variantName, inverted );
993}
994
996{
998}
999
1005
1007{
1008 QgsCptCityColorRamp *ramp = new QgsCptCityColorRamp( QString(), QString(), mInverted, false );
1009 ramp->copy( this );
1010 return ramp;
1011}
1012
1014{
1015 if ( ! other )
1016 return;
1017 mColor1 = other->color1();
1018 mColor2 = other->color2();
1019 mDiscrete = other->isDiscrete();
1020 mStops = other->stops();
1021 mSchemeName = other->mSchemeName;
1022 mVariantName = other->mVariantName;
1023 mVariantList = other->mVariantList;
1024 mFileLoaded = other->mFileLoaded;
1025 mInverted = other->mInverted;
1026}
1027
1029{
1030 QgsGradientColorRamp *ramp =
1032 // add author and copyright information
1033 // TODO also add COPYING.xml file/link?
1035 info[u"cpt-city-gradient"_s] = "<cpt-city>/" + mSchemeName + mVariantName + ".svg";
1036 QString copyingFilename = copyingFileName();
1037 copyingFilename.remove( QgsCptCityArchive::defaultBaseDir() );
1038 info[u"cpt-city-license"_s] = "<cpt-city>" + copyingFilename;
1039 ramp->setInfo( info );
1040 return ramp;
1041}
1042
1043
1045{
1046 QVariantMap map;
1047 map[u"schemeName"_s] = mSchemeName;
1048 map[u"variantName"_s] = mVariantName;
1049 map[u"inverted"_s] = QString::number( mInverted );
1050 map[u"rampType"_s] = type();
1051 return map;
1052}
1053
1054QString QgsCptCityColorRamp::fileNameForVariant( const QString &schema, const QString &variant )
1055{
1056 return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + schema + variant + ".svg";
1057}
1058
1060{
1061 if ( mSchemeName.isEmpty() )
1062 return QString();
1063 else
1064 {
1065 return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mSchemeName + mVariantName + ".svg";
1066 }
1067}
1068
1070{
1071 return QgsCptCityArchive::findFileName( u"COPYING.xml"_s, QFileInfo( fileName() ).dir().path(),
1073}
1074
1076{
1077 return QgsCptCityArchive::findFileName( u"DESC.xml"_s, QFileInfo( fileName() ).dir().path(),
1079}
1080
1085
1087{
1088 if ( mFileLoaded )
1089 {
1090 QgsDebugMsgLevel( "File already loaded for " + mSchemeName + mVariantName, 2 );
1091 return true;
1092 }
1093
1094 // get filename
1095 QString filename = fileName();
1096 if ( filename.isNull() )
1097 {
1098 return false;
1099 }
1100
1101 QgsDebugMsgLevel( u"filename= %1 loaded=%2"_s.arg( filename ).arg( mFileLoaded ), 2 );
1102
1103 // get color ramp from svg file
1104 QMap< double, QPair<QColor, QColor> > colorMap =
1106
1107 // add colors to palette
1108 mFileLoaded = false;
1109 mStops.clear();
1110 QMap<double, QPair<QColor, QColor> >::const_iterator it, prev;
1111 // first detect if file is gradient is continuous or discrete
1112 // discrete: stop contains 2 colors and first color is identical to previous second
1113 // multi: stop contains 2 colors and no relation with previous stop
1114 mDiscrete = false;
1115 mMultiStops = false;
1116 it = prev = colorMap.constBegin();
1117 while ( it != colorMap.constEnd() )
1118 {
1119 // look for stops that contain multiple values
1120 if ( it != colorMap.constBegin() && ( it.value().first != it.value().second ) )
1121 {
1122 if ( it.value().first == prev.value().second )
1123 {
1124 mDiscrete = true;
1125 break;
1126 }
1127 else
1128 {
1129 mMultiStops = true;
1130 break;
1131 }
1132 }
1133 prev = it;
1134 ++it;
1135 }
1136
1137 // fill all stops
1138 it = prev = colorMap.constBegin();
1139 while ( it != colorMap.constEnd() )
1140 {
1141 if ( mDiscrete )
1142 {
1143 // mPalette << qMakePair( it.key(), it.value().second );
1144 mStops.append( QgsGradientStop( it.key(), it.value().second ) );
1145 }
1146 else
1147 {
1148 // mPalette << qMakePair( it.key(), it.value().first );
1149 mStops.append( QgsGradientStop( it.key(), it.value().first ) );
1150 if ( ( mMultiStops ) &&
1151 ( it.key() != 0.0 && it.key() != 1.0 ) )
1152 {
1153 mStops.append( QgsGradientStop( it.key(), it.value().second ) );
1154 }
1155 }
1156 prev = it;
1157 ++it;
1158 }
1159
1160 // remove first and last items (mColor1 and mColor2)
1161 if ( ! mStops.isEmpty() && mStops.at( 0 ).offset == 0.0 )
1162 mColor1 = mStops.takeFirst().color;
1163 if ( ! mStops.isEmpty() && mStops.last().offset == 1.0 )
1164 mColor2 = mStops.takeLast().color;
1165
1166 if ( mInverted )
1167 {
1169 }
1170
1171 mFileLoaded = true;
1172 return true;
1173}
1174
1175
1176//
1177// QgsPresetColorRamp
1178//
1179
1181{
1182 const auto constColors = colors;
1183 for ( const QColor &color : constColors )
1184 {
1185 mColors << qMakePair( color, color.name() );
1186 }
1187 // need at least one color
1188 if ( mColors.isEmpty() )
1189 mColors << qMakePair( QColor( 250, 75, 60 ), u"#fa4b3c"_s );
1190}
1191
1193 : mColors( colors )
1194{
1195 // need at least one color
1196 if ( mColors.isEmpty() )
1197 mColors << qMakePair( QColor( 250, 75, 60 ), u"#fa4b3c"_s );
1198}
1199
1201{
1203
1204 int i = 0;
1205 QString colorString = properties.value( u"preset_color_%1"_s.arg( i ), QString() ).toString();
1206 QString colorName = properties.value( u"preset_color_name_%1"_s.arg( i ), QString() ).toString();
1207 while ( !colorString.isEmpty() )
1208 {
1209 colors << qMakePair( QgsColorUtils::colorFromString( colorString ), colorName );
1210 i++;
1211 colorString = properties.value( u"preset_color_%1"_s.arg( i ), QString() ).toString();
1212 colorName = properties.value( u"preset_color_name_%1"_s.arg( i ), QString() ).toString();
1213 }
1214
1215 return new QgsPresetSchemeColorRamp( colors );
1216}
1217
1219{
1220 QList< QColor > l;
1221 l.reserve( mColors.count() );
1222 for ( int i = 0; i < mColors.count(); ++i )
1223 {
1224 l << mColors.at( i ).first;
1225 }
1226 return l;
1227}
1228
1229double QgsPresetSchemeColorRamp::value( int index ) const
1230{
1231 if ( mColors.empty() )
1232 return 0;
1233 return static_cast< double >( index ) / ( mColors.size() - 1 );
1234}
1235
1237{
1238 if ( value < 0 || value > 1 )
1239 return QColor();
1240
1241 int colorCnt = mColors.count();
1242 int colorIdx = std::min( static_cast< int >( value * colorCnt ), colorCnt - 1 );
1243
1244 if ( colorIdx >= 0 && colorIdx < colorCnt )
1245 return mColors.at( colorIdx ).first;
1246
1247 return QColor();
1248}
1249
1254
1256{
1257 QgsNamedColorList tmpColors;
1258
1259 for ( int k = mColors.size() - 1; k >= 0; k-- )
1260 {
1261 tmpColors << mColors.at( k );
1262 }
1263 mColors = tmpColors;
1264}
1265
1270
1272{
1273 QVariantMap props;
1274 for ( int i = 0; i < mColors.count(); ++i )
1275 {
1276 props.insert( u"preset_color_%1"_s.arg( i ), QgsColorUtils::colorToString( mColors.at( i ).first ) );
1277 props.insert( u"preset_color_name_%1"_s.arg( i ), mColors.at( i ).second );
1278 }
1279 props[u"rampType"_s] = type();
1280 return props;
1281}
1282
1284{
1285 return mColors.count();
1286}
1287
1289{
1290 return mColors;
1291}
AngularDirection
Angular directions.
Definition qgis.h:3491
@ NoOrientation
Unknown orientation or sentinel value.
Definition qgis.h:3494
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3493
@ Clockwise
Clockwise direction.
Definition qgis.h:3492
void invert() override
Inverts the ordering of the color ramp.
static QList< int > listSchemeVariants(const QString &schemeName)
Returns a list of the valid variants (numbers of colors) for a specified color brewer scheme name.
QColor color(double value) const override
Returns the color corresponding to a specified value.
QgsColorBrewerColorRamp * clone() const override
Creates a clone of the color ramp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsColorBrewerColorRamp color ramp created using the properties encoded in a string map...
static QStringList listSchemeNames()
Returns a list of all valid color brewer scheme names.
QString type() const override
Returns a string representing the color ramp type.
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
QString schemeName() const
Returns the name of the color brewer color scheme.
int colors() const
Returns the number of colors in the ramp.
QVariantMap properties() const override
Returns a string map containing all the color ramp's properties.
QgsColorBrewerColorRamp(const QString &schemeName=DEFAULT_COLORBREWER_SCHEMENAME, int colors=DEFAULT_COLORBREWER_COLORS, bool inverted=false)
Constructor for QgsColorBrewerColorRamp.
void loadPalette()
Generates the scheme using the current name and number of colors.
static QStringList listSchemes()
static QList< QColor > listSchemeColors(const QString &schemeName, int colors)
static QList< int > listSchemeVariants(const QString &schemeName)
Abstract base class for color ramps.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
static QString defaultBaseDir()
static QMap< QString, QString > copyingInfo(const QString &fileName)
static QString findFileName(const QString &target, const QString &startDir, const QString &baseDir)
static QMap< double, QPair< QColor, QColor > > gradientColorMap(const QString &fileName)
QgsCptCityColorRamp * clone() const override
Creates a clone of the color ramp.
QgsStringMap copyingInfo() const
QVariantMap properties() const override
Returns a string map containing all the color ramp's properties.
QgsCptCityColorRamp(const QString &schemeName=DEFAULT_CPTCITY_SCHEMENAME, const QString &variantName=DEFAULT_CPTCITY_VARIANTNAME, bool inverted=false, bool doLoadFile=true)
Constructor for QgsCptCityColorRamp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
QStringList variantList() const
void copy(const QgsCptCityColorRamp *other)
static QString fileNameForVariant(const QString &schema, const QString &variant)
Returns the source file name for a CPT schema and variant.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
QString descFileName() const
QgsGradientColorRamp * cloneGradientRamp() const
QString copyingFileName() const
void invert() override
Inverts the ordering of the color ramp.
QString type() const override
Returns a string representing the color ramp type.
QString schemeName() const
QString variantName() const
QgsGradientStopsList mStops
void setInfo(const QgsStringMap &info)
Sets additional info to attach to the gradient ramp (e.g., authorship notes).
bool isDiscrete() const
Returns true if the gradient is using discrete interpolation, rather than smoothly interpolating betw...
void setColorSpec(QColor::Spec spec)
Sets the color specification in which the color component interpolation will occur.
QVariantMap properties() const override
Returns a string map containing all the color ramp's properties.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
QgsStringMap info() const
Returns any additional info attached to the gradient ramp (e.g., authorship notes).
void convertToDiscrete(bool discrete)
Converts a gradient with existing color stops to or from discrete interpolation.
Qgis::AngularDirection mDirection
QColor color(double value) const override
Returns the color corresponding to a specified value.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
void setStops(const QgsGradientStopsList &stops)
Sets the list of intermediate gradient stops for the ramp.
QString type() const override
Returns a string representing the color ramp type.
QgsGradientColorRamp(const QColor &color1=DEFAULT_GRADIENT_COLOR1, const QColor &color2=DEFAULT_GRADIENT_COLOR2, bool discrete=false, const QgsGradientStopsList &stops=QgsGradientStopsList())
Constructor for QgsGradientColorRamp.
QColor color1() const
Returns the gradient start color.
void setDirection(Qgis::AngularDirection direction)
Sets the direction to traverse the color wheel using when interpolating hue-based color specification...
Qgis::AngularDirection direction() const
Returns the direction to traverse the color wheel using when interpolating hue-based color specificat...
void invert() override
Inverts the ordering of the color ramp.
void addStopsToGradient(QGradient *gradient, double opacity=1) const
Copy color ramp stops to a QGradient.
QgsGradientStopsList stops() const
Returns the list of intermediate gradient stops for the ramp.
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
QgsGradientColorRamp * clone() const override
Creates a clone of the color ramp.
InterpolateColorFunc mFunc
QColor color2() const
Returns the gradient end color.
Represents a color stop within a QgsGradientColorRamp color ramp.
void setColorSpec(QColor::Spec spec)
Sets the color specification in which the color component interpolation will occur.
double offset
Relative positional offset, between 0 and 1.
QColor color
Gradient color at stop.
QgsGradientStop(double offset, const QColor &color)
Constructor for QgsGradientStop.
static QString typeString()
Returns the string identifier for QgsLimitedRandomColorRamp.
void updateColors()
Must be called after changing the properties of the color ramp to regenerate the list of random color...
static QList< QColor > randomColors(int count, int hueMax=DEFAULT_RANDOM_HUE_MAX, int hueMin=DEFAULT_RANDOM_HUE_MIN, int satMax=DEFAULT_RANDOM_SAT_MAX, int satMin=DEFAULT_RANDOM_SAT_MIN, int valMax=DEFAULT_RANDOM_VAL_MAX, int valMin=DEFAULT_RANDOM_VAL_MIN)
Gets a list of random colors.
int count() const override
Returns number of defined colors, or -1 if undefined.
QColor color(double value) const override
Returns the color corresponding to a specified value.
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
QVariantMap properties() const override
Returns a string map containing all the color ramp's properties.
int valMax() const
Returns the maximum value for generated colors.
QString type() const override
Returns a string representing the color ramp type.
int satMax() const
Returns the maximum saturation for generated colors.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsLimitedRandomColorRamp color ramp created using the properties encoded in a string m...
QgsLimitedRandomColorRamp * clone() const override
Creates a clone of the color ramp.
int hueMax() const
Returns the maximum hue for generated colors.
int hueMin() const
Returns the minimum hue for generated colors.
int valMin() const
Returns the minimum value for generated colors.
QgsLimitedRandomColorRamp(int count=DEFAULT_RANDOM_COUNT, int hueMin=DEFAULT_RANDOM_HUE_MIN, int hueMax=DEFAULT_RANDOM_HUE_MAX, int satMin=DEFAULT_RANDOM_SAT_MIN, int satMax=DEFAULT_RANDOM_SAT_MAX, int valMin=DEFAULT_RANDOM_VAL_MIN, int valMax=DEFAULT_RANDOM_VAL_MAX)
Constructor for QgsLimitedRandomColorRamp.
int satMin() const
Returns the minimum saturation for generated colors.
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
QColor color(double value) const override
Returns the color corresponding to a specified value.
QString type() const override
Returns a string representing the color ramp type.
QList< QColor > colors() const
Returns the list of colors used by the ramp.
static QString typeString()
Returns the string identifier for QgsPresetSchemeColorRamp.
void invert() override
Inverts the ordering of the color ramp.
QVariantMap properties() const override
Returns a string map containing all the color ramp's properties.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsPresetSchemeColorRamp color ramp created using the properties encoded in a string ma...
int count() const override
Returns number of defined colors, or -1 if undefined.
QgsPresetSchemeColorRamp(const QList< QColor > &colors=QList< QColor >())
Constructor for QgsPresetSchemeColorRamp.
QgsNamedColorList fetchColors(const QString &context=QString(), const QColor &baseColor=QColor()) override
Gets a list of colors from the scheme.
QgsPresetSchemeColorRamp * clone() const override
Creates a clone of the color ramp.
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
QList< QColor > mPrecalculatedColors
QgsRandomColorRamp * clone() const override
Creates a clone of the color ramp.
QgsRandomColorRamp()=default
static QString typeString()
Returns the string identifier for QgsRandomColorRamp.
int count() const override
Returns number of defined colors, or -1 if undefined.
QString type() const override
Returns a string representing the color ramp type.
virtual void setTotalColorCount(int colorCount)
Sets the desired total number of unique colors for the resultant ramp.
QVariantMap properties() const override
Returns a string map containing all the color ramp's properties.
QColor color(double value) const override
Returns the color corresponding to a specified value.
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
QMap< QString, QString > QgsStringMap
Definition qgis.h:7413
bool stopLessThan(const QgsGradientStop &s1, const QgsGradientStop &s2)
#define DEFAULT_COLORBREWER_COLORS
#define DEFAULT_COLORBREWER_SCHEMENAME
#define DEFAULT_RANDOM_HUE_MAX
#define DEFAULT_CPTCITY_SCHEMENAME
#define DEFAULT_RANDOM_HUE_MIN
#define DEFAULT_RANDOM_COUNT
#define DEFAULT_RANDOM_SAT_MAX
#define DEFAULT_RANDOM_SAT_MIN
#define DEFAULT_CPTCITY_VARIANTNAME
#define DEFAULT_GRADIENT_COLOR1
#define DEFAULT_RANDOM_VAL_MIN
QList< QgsGradientStop > QgsGradientStopsList
List of gradient stops.
#define DEFAULT_GRADIENT_COLOR2
#define DEFAULT_RANDOM_VAL_MAX
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63