QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
215void QgsGradientStop::setColorSpec( QColor::Spec spec )
216{
217 mColorSpec = spec;
218
219 switch ( mColorSpec )
220 {
221 case QColor::Rgb:
222 case QColor::Invalid:
223 case QColor::ExtendedRgb:
224 mFunc = _interpolateRgb;
225 break;
226 case QColor::Cmyk:
227 mFunc = interpolateCmyk;
228 break;
229 case QColor::Hsv:
230 mFunc = _interpolateHsv;
231 break;
232 case QColor::Hsl:
233 mFunc = _interpolateHsl;
234 break;
235 }
236}
237
238QgsGradientColorRamp::QgsGradientColorRamp( const QColor &color1, const QColor &color2, bool discrete, const QgsGradientStopsList &stops )
239 : mColor1( color1 )
240 , mColor2( color2 )
241 , mDiscrete( discrete )
242 , mStops( stops )
243 , mFunc( _interpolateRgb )
244{}
245
246QgsColorRamp *QgsGradientColorRamp::create( const QVariantMap &props )
247{
248 // color1 and color2
251 if ( props.contains( u"color1"_s ) )
252 color1 = QgsColorUtils::colorFromString( props[u"color1"_s].toString() );
253 if ( props.contains( u"color2"_s ) )
254 color2 = QgsColorUtils::colorFromString( props[u"color2"_s].toString() );
255
256 //stops
258 if ( props.contains( u"stops"_s ) )
259 {
260 const thread_local QRegularExpression rx( u"(?<!,rgb)(?<!,cmyk)(?<!,hsl)(?<!,hsv):"_s );
261 const auto constSplit = props[u"stops"_s].toString().split( rx );
262 for ( const QString &stop : constSplit )
263 {
264 const QStringList parts = stop.split( ';' );
265 if ( parts.size() != 2 && parts.size() != 4 )
266 continue;
267
268 QColor c = QgsColorUtils::colorFromString( parts.at( 1 ) );
269 stops.append( QgsGradientStop( parts.at( 0 ).toDouble(), c ) );
270
271 if ( parts.size() == 4 )
272 {
273 if ( parts.at( 2 ).compare( "rgb"_L1 ) == 0 )
274 stops.last().setColorSpec( QColor::Spec::Rgb );
275 else if ( parts.at( 2 ).compare( "hsv"_L1 ) == 0 )
276 stops.last().setColorSpec( QColor::Spec::Hsv );
277 else if ( parts.at( 2 ).compare( "hsl"_L1 ) == 0 )
278 stops.last().setColorSpec( QColor::Spec::Hsl );
279
280 if ( parts.at( 3 ).compare( "cw"_L1 ) == 0 )
281 stops.last().setDirection( Qgis::AngularDirection::Clockwise );
282 else if ( parts.at( 3 ).compare( "ccw"_L1 ) == 0 )
283 stops.last().setDirection( Qgis::AngularDirection::CounterClockwise );
284 }
285 }
286 }
287
288 // discrete vs. continuous
289 bool discrete = false;
290 if ( props.contains( u"discrete"_s ) )
291 {
292 if ( props[u"discrete"_s] == "1"_L1 )
293 discrete = true;
294 }
295
296 // search for information keys starting with "info_"
298 for ( QVariantMap::const_iterator it = props.constBegin(); it != props.constEnd(); ++it )
299 {
300 if ( it.key().startsWith( "info_"_L1 ) )
301 info[it.key().mid( 5 )] = it.value().toString();
302 }
303
305 r->setInfo( info );
306
307 if ( props.contains( u"spec"_s ) )
308 {
309 const QString spec = props.value( u"spec"_s ).toString().trimmed();
310 if ( spec.compare( "rgb"_L1 ) == 0 )
311 r->setColorSpec( QColor::Spec::Rgb );
312 else if ( spec.compare( "hsv"_L1 ) == 0 )
313 r->setColorSpec( QColor::Spec::Hsv );
314 else if ( spec.compare( "hsl"_L1 ) == 0 )
315 r->setColorSpec( QColor::Spec::Hsl );
316 }
317
318 if ( props.contains( u"direction"_s ) )
319 {
320 const QString direction = props.value( u"direction"_s ).toString().trimmed();
321 if ( direction.compare( "ccw"_L1 ) == 0 )
323 else if ( direction.compare( "cw"_L1 ) == 0 )
325 }
326
327 return r;
328}
329
330double QgsGradientColorRamp::value( int index ) const
331{
332 if ( index <= 0 )
333 {
334 return 0;
335 }
336 else if ( index >= mStops.size() + 1 )
337 {
338 return 1;
339 }
340 else
341 {
342 return mStops[index - 1].offset;
343 }
344}
345
346QColor QgsGradientColorRamp::color( double value ) const
347{
348 if ( qgsDoubleNear( value, 0.0 ) || value < 0.0 )
349 {
350 return mColor1;
351 }
352 else if ( qgsDoubleNear( value, 1.0 ) || value > 1.0 )
353 {
354 return mColor2;
355 }
356 else if ( mStops.isEmpty() )
357 {
358 if ( mDiscrete )
359 return mColor1;
360
361 return mFunc( mColor1, mColor2, value, mDirection );
362 }
363 else
364 {
365 double lower = 0, upper = 0;
366 QColor c1 = mColor1, c2;
367 for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
368 {
369 if ( it->offset > value )
370 {
371 if ( mDiscrete )
372 return c1;
373
374 upper = it->offset;
375 c2 = it->color;
376
377 return qgsDoubleNear( upper, lower ) ? c1 : it->mFunc( c1, c2, ( value - lower ) / ( upper - lower ), it->mDirection );
378 }
379 lower = it->offset;
380 c1 = it->color;
381 }
382
383 if ( mDiscrete )
384 return c1;
385
386 upper = 1;
387 c2 = mColor2;
388 return qgsDoubleNear( upper, lower ) ? c1 : mFunc( c1, c2, ( value - lower ) / ( upper - lower ), mDirection );
389 }
390}
391
393{
395}
396
398{
399 QgsGradientStopsList newStops;
400 newStops.reserve( mStops.size() );
401
402 if ( mDiscrete )
403 {
405 mColor1 = mStops.at( mStops.size() - 1 ).color;
406 for ( int k = mStops.size() - 1; k >= 1; k-- )
407 {
408 newStops << QgsGradientStop( 1 - mStops.at( k ).offset, mStops.at( k - 1 ).color );
409 }
410 newStops << QgsGradientStop( 1 - mStops.at( 0 ).offset, mColor2 );
411 }
412 else
413 {
414 QColor tmpColor = mColor2;
416 mColor1 = tmpColor;
417 for ( int k = mStops.size() - 1; k >= 0; k-- )
418 {
419 newStops << QgsGradientStop( 1 - mStops.at( k ).offset, mStops.at( k ).color );
420 }
421 }
422
423 // transfer color spec, invert directions
424 if ( mStops.empty() )
425 {
426 // reverse direction
428 }
429 else
430 {
431 newStops[0].setColorSpec( mColorSpec );
433 for ( int i = 1, j = mStops.size() - 1; i < mStops.size(); ++i, --j )
434 {
435 newStops[i].setColorSpec( mStops.at( j ).colorSpec() );
437 }
438 mColorSpec = mStops.at( 0 ).colorSpec();
440 }
441
442 mStops = newStops;
443}
444
453
455{
456 QVariantMap map;
457 map[u"color1"_s] = QgsColorUtils::colorToString( mColor1 );
458 map[u"color2"_s] = QgsColorUtils::colorToString( mColor2 );
459 if ( !mStops.isEmpty() )
460 {
461 QStringList lst;
462 lst.reserve( mStops.size() );
463 for ( const QgsGradientStop &stop : mStops )
464 {
465 lst.append( u"%1;%2;%3;%4"_s.arg( stop.offset )
466 .arg(
467 QgsColorUtils::colorToString( stop.color ),
468 stop.colorSpec() == QColor::Rgb ? u"rgb"_s
469 : stop.colorSpec() == QColor::Hsv ? u"hsv"_s
470 : stop.colorSpec() == QColor::Hsl ? u"hsl"_s
471 : QString(),
472 stop.direction() == Qgis::AngularDirection::CounterClockwise ? u"ccw"_s : u"cw"_s
473 ) );
474 }
475 map[u"stops"_s] = lst.join( ':'_L1 );
476 }
477
478 map[u"discrete"_s] = mDiscrete ? "1" : "0";
479
480 for ( QgsStringMap::const_iterator it = mInfo.constBegin(); it != mInfo.constEnd(); ++it )
481 {
482 map["info_" + it.key()] = it.value();
483 }
484
485 switch ( mColorSpec )
486 {
487 case QColor::Rgb:
488 map[u"spec"_s] = u"rgb"_s;
489 break;
490 case QColor::Hsv:
491 map[u"spec"_s] = u"hsv"_s;
492 break;
493 case QColor::Hsl:
494 map[u"spec"_s] = u"hsl"_s;
495 break;
496 case QColor::Cmyk:
497 case QColor::Invalid:
498 case QColor::ExtendedRgb:
499 break;
500 }
501
502 switch ( mDirection )
503 {
505 map[u"direction"_s] = u"cw"_s;
506 break;
508 map[u"direction"_s] = u"ccw"_s;
509 break;
511 break;
512 }
513
514 map[u"rampType"_s] = type();
515 return map;
516}
518{
519 if ( discrete == mDiscrete )
520 return;
521
522 // if going to/from Discrete, re-arrange stops
523 // this will only work when stops are equally-spaced
524 QgsGradientStopsList newStops;
525 if ( discrete )
526 {
527 // re-arrange stops offset
528 int numStops = mStops.count() + 2;
529 int i = 1;
530 for ( QgsGradientStopsList::const_iterator it = mStops.constBegin(); it != mStops.constEnd(); ++it )
531 {
532 newStops.append( QgsGradientStop( static_cast< double >( i ) / numStops, it->color ) );
533 if ( i == numStops - 1 )
534 break;
535 i++;
536 }
537 // replicate last color
538 newStops.append( QgsGradientStop( static_cast< double >( i ) / numStops, mColor2 ) );
539 }
540 else
541 {
542 // re-arrange stops offset, remove duplicate last color
543 int numStops = mStops.count() + 2;
544 int i = 1;
545 for ( QgsGradientStopsList::const_iterator it = mStops.constBegin(); it != mStops.constEnd(); ++it )
546 {
547 newStops.append( QgsGradientStop( static_cast< double >( i ) / ( numStops - 2 ), it->color ) );
548 if ( i == numStops - 3 )
549 break;
550 i++;
551 }
552 }
553 mStops = newStops;
554 mDiscrete = discrete;
555}
556
557bool stopLessThan( const QgsGradientStop &s1, const QgsGradientStop &s2 )
558{
559 return s1.offset < s2.offset;
560}
561
563{
564 mStops = stops;
565
566 //sort stops by offset
567 std::sort( mStops.begin(), mStops.end(), stopLessThan );
568}
569
570void QgsGradientColorRamp::addStopsToGradient( QGradient *gradient, double opacity ) const
571{
572 //copy color ramp stops to a QGradient
573 QColor color1 = mColor1;
574 QColor color2 = mColor2;
575 if ( opacity < 1 )
576 {
577 color1.setAlpha( color1.alpha() * opacity );
578 color2.setAlpha( color2.alpha() * opacity );
579 }
580 gradient->setColorAt( 0, color1 );
581 gradient->setColorAt( 1, color2 );
582
583 double lastOffset = 0;
584 for ( const QgsGradientStop &stop : mStops )
585 {
586 QColor rampColor = stop.color;
587 if ( opacity < 1 )
588 {
589 rampColor.setAlpha( rampColor.alpha() * opacity );
590 }
591 gradient->setColorAt( stop.offset, rampColor );
592
593 if ( stop.colorSpec() != QColor::Rgb )
594 {
595 // QGradient only supports RGB interpolation. For other color specs we have
596 // to "fake" things by populating the gradient with additional stops
597 for ( double offset = lastOffset + 0.05; offset < stop.offset; offset += 0.05 )
598 {
599 QColor midColor = color( offset );
600 if ( opacity < 1 )
601 {
602 midColor.setAlpha( midColor.alpha() * opacity );
603 }
604 gradient->setColorAt( offset, midColor );
605 }
606 }
607 lastOffset = stop.offset;
608 }
609
610 if ( mColorSpec != QColor::Rgb )
611 {
612 for ( double offset = lastOffset + 0.05; offset < 1; offset += 0.05 )
613 {
614 QColor midColor = color( offset );
615 if ( opacity < 1 )
616 {
617 midColor.setAlpha( midColor.alpha() * opacity );
618 }
619 gradient->setColorAt( offset, midColor );
620 }
621 }
622}
623
624void QgsGradientColorRamp::setColorSpec( QColor::Spec spec )
625{
626 mColorSpec = spec;
627 switch ( mColorSpec )
628 {
629 case QColor::Rgb:
630 case QColor::Invalid:
631 case QColor::ExtendedRgb:
632 mFunc = _interpolateRgb;
633 break;
634 case QColor::Cmyk:
635 mFunc = interpolateCmyk;
636 break;
637 case QColor::Hsv:
638 mFunc = _interpolateHsv;
639 break;
640 case QColor::Hsl:
641 mFunc = _interpolateHsl;
642 break;
643 }
644}
645
646
648
649
651 : mCount( count )
652 , mHueMin( hueMin )
653 , mHueMax( hueMax )
654 , mSatMin( satMin )
655 , mSatMax( satMax )
656 , mValMin( valMin )
657 , mValMax( valMax )
658{
659 updateColors();
660}
661
663{
668
669 if ( props.contains( u"count"_s ) )
670 count = props[u"count"_s].toInt();
671 if ( props.contains( u"hueMin"_s ) )
672 hueMin = props[u"hueMin"_s].toInt();
673 if ( props.contains( u"hueMax"_s ) )
674 hueMax = props[u"hueMax"_s].toInt();
675 if ( props.contains( u"satMin"_s ) )
676 satMin = props[u"satMin"_s].toInt();
677 if ( props.contains( u"satMax"_s ) )
678 satMax = props[u"satMax"_s].toInt();
679 if ( props.contains( u"valMin"_s ) )
680 valMin = props[u"valMin"_s].toInt();
681 if ( props.contains( u"valMax"_s ) )
682 valMax = props[u"valMax"_s].toInt();
683
685}
686
687double QgsLimitedRandomColorRamp::value( int index ) const
688{
689 if ( mColors.empty() )
690 return 0;
691 return static_cast< double >( index ) / ( mColors.size() - 1 );
692}
693
695{
696 if ( value < 0 || value > 1 )
697 return QColor();
698
699 int colorCnt = mColors.count();
700 int colorIdx = std::min( static_cast< int >( value * colorCnt ), colorCnt - 1 );
701
702 if ( colorIdx >= 0 && colorIdx < colorCnt )
703 return mColors.at( colorIdx );
704
705 return QColor();
706}
707
712
717
719{
720 QVariantMap map;
721 map[u"count"_s] = QString::number( mCount );
722 map[u"hueMin"_s] = QString::number( mHueMin );
723 map[u"hueMax"_s] = QString::number( mHueMax );
724 map[u"satMin"_s] = QString::number( mSatMin );
725 map[u"satMax"_s] = QString::number( mSatMax );
726 map[u"valMin"_s] = QString::number( mValMin );
727 map[u"valMax"_s] = QString::number( mValMax );
728 map[u"rampType"_s] = type();
729 return map;
730}
731
732QList<QColor> QgsLimitedRandomColorRamp::randomColors( int count, int hueMax, int hueMin, int satMax, int satMin, int valMax, int valMin )
733{
734 int h, s, v;
735 QList<QColor> colors;
736
737 //normalize values
738 int safeHueMax = std::max( hueMin, hueMax );
739 int safeHueMin = std::min( hueMin, hueMax );
740 int safeSatMax = std::max( satMin, satMax );
741 int safeSatMin = std::min( satMin, satMax );
742 int safeValMax = std::max( valMin, valMax );
743 int safeValMin = std::min( valMin, valMax );
744
745 //start hue at random angle
746 double currentHueAngle = 360.0 * static_cast< double >( std::rand() ) / RAND_MAX;
747
748 colors.reserve( count );
749 for ( int i = 0; i < count; ++i )
750 {
751 //increment hue by golden ratio (approx 137.507 degrees)
752 //as this minimizes hue nearness as count increases
753 //see http://basecase.org/env/on-rainbows for more details
754 currentHueAngle += 137.50776;
755 //scale hue to between hueMax and hueMin
756 h = std::clamp( std::round( ( std::fmod( currentHueAngle, 360.0 ) / 360.0 ) * ( safeHueMax - safeHueMin ) + safeHueMin ), 0.0, 359.0 );
757 s = std::clamp( ( static_cast<int>( std::rand() ) % ( safeSatMax - safeSatMin + 1 ) ) + safeSatMin, 0, 255 );
758 v = std::clamp( ( static_cast<int>( std::rand() ) % ( safeValMax - safeValMin + 1 ) ) + safeValMin, 0, 255 );
759 colors.append( QColor::fromHsv( h, s, v ) );
760 }
761 return colors;
762}
763
768
770
772{
773 return -1;
774}
775
776double QgsRandomColorRamp::value( int index ) const
777{
778 Q_UNUSED( index )
779 return 0.0;
780}
781
782QColor QgsRandomColorRamp::color( double value ) const
783{
784 int minVal = 130;
785 int maxVal = 255;
786
787 //if value is nan, then use last precalculated color
788 if ( std::isnan( value ) )
789 {
790 value = 1.0;
791 }
792 // Caller has converted an index into a value in [0.0, 1.0]
793 // by doing "index / (mTotalColorCount - 1)"; retrieve the original index.
794 int colorIndex = std::round( value * ( mTotalColorCount - 1 ) );
795 if ( mTotalColorCount >= 1 && mPrecalculatedColors.length() > colorIndex )
796 {
797 //use precalculated hue
798 return mPrecalculatedColors.at( colorIndex );
799 }
800
801 //can't use precalculated hues, use a totally random hue
802 int h = static_cast< int >( 360.0 * std::rand() / ( RAND_MAX + 1.0 ) );
803 int s = ( std::rand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
804 int v = ( std::rand() % ( maxVal - minVal + 1 ) ) + minVal;
805 return QColor::fromHsv( h, s, v );
806}
807
808void QgsRandomColorRamp::setTotalColorCount( const int colorCount )
809{
810 //calculate colors in advance, so that we can ensure they are more visually distinct than pure random colors
811 mPrecalculatedColors.clear();
812 mTotalColorCount = colorCount;
813
814 //This works OK for low color counts, but for > 10 or so colors there's still a good chance of
815 //similar colors being picked. TODO - investigate alternative "n-visually distinct color" routines
816
817 //random offsets
818 double hueOffset = ( 360.0 * std::rand() / ( RAND_MAX + 1.0 ) );
819
820 //try to maximise difference between hues. this is not an ideal implementation, as constant steps
821 //through the hue wheel are not visually perceived as constant changes in hue
822 //(for instance, we are much more likely to get green hues than yellow hues)
823 double hueStep = 359.0 / colorCount;
824 double currentHue = hueOffset;
825
826 //build up a list of colors
827 for ( int idx = 0; idx < colorCount; ++idx )
828 {
829 int h = static_cast< int >( std::round( currentHue ) ) % 360;
830 int s = ( std::rand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
831 int v = ( std::rand() % ( DEFAULT_RANDOM_VAL_MAX - DEFAULT_RANDOM_VAL_MIN + 1 ) ) + DEFAULT_RANDOM_VAL_MIN;
832 mPrecalculatedColors << QColor::fromHsv( h, s, v );
833 currentHue += hueStep;
834 }
835
836 //lastly, shuffle color list
837 std::random_device rd;
838 std::mt19937 g( rd() );
839 std::shuffle( mPrecalculatedColors.begin(), mPrecalculatedColors.end(), g );
840}
841
843{
845}
846
851
853{
854 return QVariantMap();
855}
856
858
861 , mColors( colors )
862 , mInverted( inverted )
863{
864 loadPalette();
865}
866
868{
871 bool inverted = false;
872
873 if ( props.contains( u"schemeName"_s ) )
874 schemeName = props[u"schemeName"_s].toString();
875 if ( props.contains( u"colors"_s ) )
876 colors = props[u"colors"_s].toInt();
877 if ( props.contains( u"inverted"_s ) )
878 inverted = props[u"inverted"_s].toInt();
879
880 return new QgsColorBrewerColorRamp( schemeName, colors, inverted );
881}
882
884{
886
887 if ( mInverted )
888 {
889 QList<QColor> tmpPalette;
890
891 for ( int k = mPalette.size() - 1; k >= 0; k-- )
892 {
893 tmpPalette << mPalette.at( k );
894 }
895 mPalette = tmpPalette;
896 }
897}
898
903
908
909double QgsColorBrewerColorRamp::value( int index ) const
910{
911 if ( mPalette.empty() )
912 return 0;
913 return static_cast< double >( index ) / ( mPalette.size() - 1 );
914}
915
917{
918 if ( mPalette.isEmpty() || value < 0 || value > 1 || std::isnan( value ) )
919 return QColor();
920
921 int paletteEntry = static_cast< int >( value * mPalette.count() );
922 if ( paletteEntry >= mPalette.count() )
923 paletteEntry = mPalette.count() - 1;
924 return mPalette.at( paletteEntry );
925}
926
932
937
939{
940 QVariantMap map;
941 map[u"schemeName"_s] = mSchemeName;
942 map[u"colors"_s] = QString::number( mColors );
943 map[u"inverted"_s] = QString::number( mInverted );
944 map[u"rampType"_s] = type();
945 return map;
946}
947
948
950
951
952QgsCptCityColorRamp::QgsCptCityColorRamp( const QString &schemeName, const QString &variantName, bool inverted, bool doLoadFile )
956 , mInverted( inverted )
957{
958 // TODO replace this with hard-coded data in the default case
959 // don't load file if variant is missing
960 if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
961 loadFile();
962}
963
964QgsCptCityColorRamp::QgsCptCityColorRamp( const QString &schemeName, const QStringList &variantList, 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{
1031 // add author and copyright information
1032 // TODO also add COPYING.xml file/link?
1034 info[u"cpt-city-gradient"_s] = "<cpt-city>/" + mSchemeName + mVariantName + ".svg";
1035 QString copyingFilename = copyingFileName();
1036 copyingFilename.remove( QgsCptCityArchive::defaultBaseDir() );
1037 info[u"cpt-city-license"_s] = "<cpt-city>" + copyingFilename;
1038 ramp->setInfo( info );
1039 return ramp;
1040}
1041
1042
1044{
1045 QVariantMap map;
1046 map[u"schemeName"_s] = mSchemeName;
1047 map[u"variantName"_s] = mVariantName;
1048 map[u"inverted"_s] = QString::number( mInverted );
1049 map[u"rampType"_s] = type();
1050 return map;
1051}
1052
1053QString QgsCptCityColorRamp::fileNameForVariant( const QString &schema, const QString &variant )
1054{
1055 return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + schema + variant + ".svg";
1056}
1057
1059{
1060 if ( mSchemeName.isEmpty() )
1061 return QString();
1062 else
1063 {
1064 return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mSchemeName + mVariantName + ".svg";
1065 }
1066}
1067
1069{
1070 return QgsCptCityArchive::findFileName( u"COPYING.xml"_s, QFileInfo( fileName() ).dir().path(), QgsCptCityArchive::defaultBaseDir() );
1071}
1072
1074{
1075 return QgsCptCityArchive::findFileName( u"DESC.xml"_s, QFileInfo( fileName() ).dir().path(), QgsCptCityArchive::defaultBaseDir() );
1076}
1077
1082
1084{
1085 if ( mFileLoaded )
1086 {
1087 QgsDebugMsgLevel( "File already loaded for " + mSchemeName + mVariantName, 2 );
1088 return true;
1089 }
1090
1091 // get filename
1092 QString filename = fileName();
1093 if ( filename.isNull() )
1094 {
1095 return false;
1096 }
1097
1098 QgsDebugMsgLevel( u"filename= %1 loaded=%2"_s.arg( filename ).arg( mFileLoaded ), 2 );
1099
1100 // get color ramp from svg file
1101 QMap< double, QPair<QColor, QColor> > colorMap = QgsCptCityArchive::gradientColorMap( filename );
1102
1103 // add colors to palette
1104 mFileLoaded = false;
1105 mStops.clear();
1106 QMap<double, QPair<QColor, QColor> >::const_iterator it, prev;
1107 // first detect if file is gradient is continuous or discrete
1108 // discrete: stop contains 2 colors and first color is identical to previous second
1109 // multi: stop contains 2 colors and no relation with previous stop
1110 mDiscrete = false;
1111 mMultiStops = false;
1112 it = prev = colorMap.constBegin();
1113 while ( it != colorMap.constEnd() )
1114 {
1115 // look for stops that contain multiple values
1116 if ( it != colorMap.constBegin() && ( it.value().first != it.value().second ) )
1117 {
1118 if ( it.value().first == prev.value().second )
1119 {
1120 mDiscrete = true;
1121 break;
1122 }
1123 else
1124 {
1125 mMultiStops = true;
1126 break;
1127 }
1128 }
1129 prev = it;
1130 ++it;
1131 }
1132
1133 // fill all stops
1134 it = prev = colorMap.constBegin();
1135 while ( it != colorMap.constEnd() )
1136 {
1137 if ( mDiscrete )
1138 {
1139 // mPalette << qMakePair( it.key(), it.value().second );
1140 mStops.append( QgsGradientStop( it.key(), it.value().second ) );
1141 }
1142 else
1143 {
1144 // mPalette << qMakePair( it.key(), it.value().first );
1145 mStops.append( QgsGradientStop( it.key(), it.value().first ) );
1146 if ( ( mMultiStops ) && ( it.key() != 0.0 && it.key() != 1.0 ) )
1147 {
1148 mStops.append( QgsGradientStop( it.key(), it.value().second ) );
1149 }
1150 }
1151 prev = it;
1152 ++it;
1153 }
1154
1155 // remove first and last items (mColor1 and mColor2)
1156 if ( !mStops.isEmpty() && mStops.at( 0 ).offset == 0.0 )
1157 mColor1 = mStops.takeFirst().color;
1158 if ( !mStops.isEmpty() && mStops.last().offset == 1.0 )
1159 mColor2 = mStops.takeLast().color;
1160
1161 if ( mInverted )
1162 {
1164 }
1165
1166 mFileLoaded = true;
1167 return true;
1168}
1169
1170
1171//
1172// QgsPresetColorRamp
1173//
1174
1176{
1177 const auto constColors = colors;
1178 for ( const QColor &color : constColors )
1179 {
1180 mColors << qMakePair( color, color.name() );
1181 }
1182 // need at least one color
1183 if ( mColors.isEmpty() )
1184 mColors << qMakePair( QColor( 250, 75, 60 ), u"#fa4b3c"_s );
1185}
1186
1188 : mColors( colors )
1189{
1190 // need at least one color
1191 if ( mColors.isEmpty() )
1192 mColors << qMakePair( QColor( 250, 75, 60 ), u"#fa4b3c"_s );
1193}
1194
1196{
1198
1199 int i = 0;
1200 QString colorString = properties.value( u"preset_color_%1"_s.arg( i ), QString() ).toString();
1201 QString colorName = properties.value( u"preset_color_name_%1"_s.arg( i ), QString() ).toString();
1202 while ( !colorString.isEmpty() )
1203 {
1204 colors << qMakePair( QgsColorUtils::colorFromString( colorString ), colorName );
1205 i++;
1206 colorString = properties.value( u"preset_color_%1"_s.arg( i ), QString() ).toString();
1207 colorName = properties.value( u"preset_color_name_%1"_s.arg( i ), QString() ).toString();
1208 }
1209
1210 return new QgsPresetSchemeColorRamp( colors );
1211}
1212
1214{
1215 QList< QColor > l;
1216 l.reserve( mColors.count() );
1217 for ( int i = 0; i < mColors.count(); ++i )
1218 {
1219 l << mColors.at( i ).first;
1220 }
1221 return l;
1222}
1223
1224double QgsPresetSchemeColorRamp::value( int index ) const
1225{
1226 if ( mColors.empty() )
1227 return 0;
1228 return static_cast< double >( index ) / ( mColors.size() - 1 );
1229}
1230
1232{
1233 if ( value < 0 || value > 1 )
1234 return QColor();
1235
1236 int colorCnt = mColors.count();
1237 int colorIdx = std::min( static_cast< int >( value * colorCnt ), colorCnt - 1 );
1238
1239 if ( colorIdx >= 0 && colorIdx < colorCnt )
1240 return mColors.at( colorIdx ).first;
1241
1242 return QColor();
1243}
1244
1249
1251{
1252 QgsNamedColorList tmpColors;
1253
1254 for ( int k = mColors.size() - 1; k >= 0; k-- )
1255 {
1256 tmpColors << mColors.at( k );
1257 }
1258 mColors = tmpColors;
1259}
1260
1265
1267{
1268 QVariantMap props;
1269 for ( int i = 0; i < mColors.count(); ++i )
1270 {
1271 props.insert( u"preset_color_%1"_s.arg( i ), QgsColorUtils::colorToString( mColors.at( i ).first ) );
1272 props.insert( u"preset_color_name_%1"_s.arg( i ), mColors.at( i ).second );
1273 }
1274 props[u"rampType"_s] = type();
1275 return props;
1276}
1277
1279{
1280 return mColors.count();
1281}
1282
1284{
1285 return mColors;
1286}
AngularDirection
Angular directions.
Definition qgis.h:3546
@ NoOrientation
Unknown orientation or sentinel value.
Definition qgis.h:3549
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3548
@ Clockwise
Clockwise direction.
Definition qgis.h:3547
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:6975
QMap< QString, QString > QgsStringMap
Definition qgis.h:7475
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