QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgscolorramp.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscolorramp.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 "qgscolorramp.h"
17 #include "qgscolorbrewerpalette.h"
18 #include "qgscptcityarchive.h"
19 
20 #include "qgssymbollayerutils.h"
21 #include "qgsapplication.h"
22 #include "qgslogger.h"
23 
24 #include <algorithm>
25 #include <random>
26 
27 #include <QTime>
28 #include <QDir>
29 #include <QFileInfo>
30 
32 
33 static QColor _interpolate( const QColor &c1, const QColor &c2, const double value )
34 {
35  if ( std::isnan( value ) ) return c2;
36 
37  qreal r = ( c1.redF() + value * ( c2.redF() - c1.redF() ) );
38  qreal g = ( c1.greenF() + value * ( c2.greenF() - c1.greenF() ) );
39  qreal b = ( c1.blueF() + value * ( c2.blueF() - c1.blueF() ) );
40  qreal a = ( c1.alphaF() + value * ( c2.alphaF() - c1.alphaF() ) );
41 
42  return QColor::fromRgbF( r, g, b, a );
43 }
44 
46 
47 QgsGradientColorRamp::QgsGradientColorRamp( const QColor &color1, const QColor &color2,
48  bool discrete, const QgsGradientStopsList &stops )
49  : mColor1( color1 )
50  , mColor2( color2 )
51  , mDiscrete( discrete )
52  , mStops( stops )
53 {
54 }
55 
56 QgsColorRamp *QgsGradientColorRamp::create( const QVariantMap &props )
57 {
58  // color1 and color2
61  if ( props.contains( QStringLiteral( "color1" ) ) )
62  color1 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color1" )].toString() );
63  if ( props.contains( QStringLiteral( "color2" ) ) )
64  color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color2" )].toString() );
65 
66  //stops
68  if ( props.contains( QStringLiteral( "stops" ) ) )
69  {
70  const auto constSplit = props["stops"].toString().split( ':' );
71  for ( const QString &stop : constSplit )
72  {
73  int i = stop.indexOf( ';' );
74  if ( i == -1 )
75  continue;
76 
77  QColor c = QgsSymbolLayerUtils::decodeColor( stop.mid( i + 1 ) );
78  stops.append( QgsGradientStop( stop.leftRef( i ).toDouble(), c ) );
79  }
80  }
81 
82  // discrete vs. continuous
83  bool discrete = false;
84  if ( props.contains( QStringLiteral( "discrete" ) ) )
85  {
86  if ( props[QStringLiteral( "discrete" )] == QLatin1String( "1" ) )
87  discrete = true;
88  }
89 
90  // search for information keys starting with "info_"
92  for ( QVariantMap::const_iterator it = props.constBegin();
93  it != props.constEnd(); ++it )
94  {
95  if ( it.key().startsWith( QLatin1String( "info_" ) ) )
96  info[ it.key().mid( 5 )] = it.value().toString();
97  }
98 
100  r->setInfo( info );
101  return r;
102 }
103 
104 double QgsGradientColorRamp::value( int index ) const
105 {
106  if ( index <= 0 )
107  {
108  return 0;
109  }
110  else if ( index >= mStops.size() + 1 )
111  {
112  return 1;
113  }
114  else
115  {
116  return mStops[index - 1].offset;
117  }
118 }
119 
120 QColor QgsGradientColorRamp::color( double value ) const
121 {
122  if ( qgsDoubleNear( value, 0.0 ) || value < 0.0 )
123  {
124  return mColor1;
125  }
126  else if ( qgsDoubleNear( value, 1.0 ) || value > 1.0 )
127  {
128  return mColor2;
129  }
130  else if ( mStops.isEmpty() )
131  {
132  if ( mDiscrete )
133  return mColor1;
134 
135  return _interpolate( mColor1, mColor2, value );
136  }
137  else
138  {
139  double lower = 0, upper = 0;
140  QColor c1 = mColor1, c2;
141  for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
142  {
143  if ( it->offset > value )
144  {
145  if ( mDiscrete )
146  return c1;
147 
148  upper = it->offset;
149  c2 = it->color;
150 
151  return qgsDoubleNear( upper, lower ) ? c1 : _interpolate( c1, c2, ( value - lower ) / ( upper - lower ) );
152  }
153  lower = it->offset;
154  c1 = it->color;
155  }
156 
157  if ( mDiscrete )
158  return c1;
159 
160  upper = 1;
161  c2 = mColor2;
162  return qgsDoubleNear( upper, lower ) ? c1 : _interpolate( c1, c2, ( value - lower ) / ( upper - lower ) );
163  }
164 }
165 
167 {
169 }
170 
172 {
173  QgsGradientStopsList newStops;
174 
175  if ( mDiscrete )
176  {
177  mColor2 = mColor1;
178  mColor1 = mStops.at( mStops.size() - 1 ).color;
179  for ( int k = mStops.size() - 1; k >= 1; k-- )
180  {
181  newStops << QgsGradientStop( 1 - mStops.at( k ).offset, mStops.at( k - 1 ).color );
182  }
183  newStops << QgsGradientStop( 1 - mStops.at( 0 ).offset, mColor2 );
184  }
185  else
186  {
187  QColor tmpColor = mColor2;
188  mColor2 = mColor1;
189  mColor1 = tmpColor;
190  for ( int k = mStops.size() - 1; k >= 0; k-- )
191  {
192  newStops << QgsGradientStop( 1 - mStops.at( k ).offset, mStops.at( k ).color );
193  }
194  }
195  mStops = newStops;
196 }
197 
199 {
201  mDiscrete, mStops );
202  r->setInfo( mInfo );
203  return r;
204 }
205 
207 {
208  QVariantMap map;
209  map[QStringLiteral( "color1" )] = QgsSymbolLayerUtils::encodeColor( mColor1 );
210  map[QStringLiteral( "color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
211  if ( !mStops.isEmpty() )
212  {
213  QStringList lst;
214  for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
215  {
216  lst.append( QStringLiteral( "%1;%2" ).arg( it->offset ).arg( QgsSymbolLayerUtils::encodeColor( it->color ) ) );
217  }
218  map[QStringLiteral( "stops" )] = lst.join( QLatin1Char( ':' ) );
219  }
220 
221  map[QStringLiteral( "discrete" )] = mDiscrete ? "1" : "0";
222 
223  for ( QgsStringMap::const_iterator it = mInfo.constBegin();
224  it != mInfo.constEnd(); ++it )
225  {
226  map["info_" + it.key()] = it.value();
227  }
228 
229  map[QStringLiteral( "rampType" )] = type();
230  return map;
231 }
233 {
234  if ( discrete == mDiscrete )
235  return;
236 
237  // if going to/from Discrete, re-arrange stops
238  // this will only work when stops are equally-spaced
239  QgsGradientStopsList newStops;
240  if ( discrete )
241  {
242  // re-arrange stops offset
243  int numStops = mStops.count() + 2;
244  int i = 1;
245  for ( QgsGradientStopsList::const_iterator it = mStops.constBegin();
246  it != mStops.constEnd(); ++it )
247  {
248  newStops.append( QgsGradientStop( static_cast< double >( i ) / numStops, it->color ) );
249  if ( i == numStops - 1 )
250  break;
251  i++;
252  }
253  // replicate last color
254  newStops.append( QgsGradientStop( static_cast< double >( i ) / numStops, mColor2 ) );
255  }
256  else
257  {
258  // re-arrange stops offset, remove duplicate last color
259  int numStops = mStops.count() + 2;
260  int i = 1;
261  for ( QgsGradientStopsList::const_iterator it = mStops.constBegin();
262  it != mStops.constEnd(); ++it )
263  {
264  newStops.append( QgsGradientStop( static_cast< double >( i ) / ( numStops - 2 ), it->color ) );
265  if ( i == numStops - 3 )
266  break;
267  i++;
268  }
269  }
270  mStops = newStops;
271  mDiscrete = discrete;
272 }
273 
274 bool stopLessThan( const QgsGradientStop &s1, const QgsGradientStop &s2 )
275 {
276  return s1.offset < s2.offset;
277 }
278 
280 {
281  mStops = stops;
282 
283  //sort stops by offset
284  std::sort( mStops.begin(), mStops.end(), stopLessThan );
285 }
286 
287 void QgsGradientColorRamp::addStopsToGradient( QGradient *gradient, double opacity )
288 {
289  //copy color ramp stops to a QGradient
290  QColor color1 = mColor1;
291  QColor color2 = mColor2;
292  if ( opacity < 1 )
293  {
294  color1.setAlpha( color1.alpha() * opacity );
295  color2.setAlpha( color2.alpha() * opacity );
296  }
297  gradient->setColorAt( 0, color1 );
298  gradient->setColorAt( 1, color2 );
299 
300  for ( QgsGradientStopsList::const_iterator it = mStops.constBegin();
301  it != mStops.constEnd(); ++it )
302  {
303  QColor rampColor = it->color;
304  if ( opacity < 1 )
305  {
306  rampColor.setAlpha( rampColor.alpha() * opacity );
307  }
308  gradient->setColorAt( it->offset, rampColor );
309  }
310 }
311 
312 
314 
315 
316 QgsLimitedRandomColorRamp::QgsLimitedRandomColorRamp( int count, int hueMin, int hueMax,
317  int satMin, int satMax, int valMin, int valMax )
318  : mCount( count )
319  , mHueMin( hueMin ), mHueMax( hueMax )
320  , mSatMin( satMin ), mSatMax( satMax )
321  , mValMin( valMin ), mValMax( valMax )
322 {
323  updateColors();
324 }
325 
327 {
332 
333  if ( props.contains( QStringLiteral( "count" ) ) ) count = props[QStringLiteral( "count" )].toInt();
334  if ( props.contains( QStringLiteral( "hueMin" ) ) ) hueMin = props[QStringLiteral( "hueMin" )].toInt();
335  if ( props.contains( QStringLiteral( "hueMax" ) ) ) hueMax = props[QStringLiteral( "hueMax" )].toInt();
336  if ( props.contains( QStringLiteral( "satMin" ) ) ) satMin = props[QStringLiteral( "satMin" )].toInt();
337  if ( props.contains( QStringLiteral( "satMax" ) ) ) satMax = props[QStringLiteral( "satMax" )].toInt();
338  if ( props.contains( QStringLiteral( "valMin" ) ) ) valMin = props[QStringLiteral( "valMin" )].toInt();
339  if ( props.contains( QStringLiteral( "valMax" ) ) ) valMax = props[QStringLiteral( "valMax" )].toInt();
340 
342 }
343 
344 double QgsLimitedRandomColorRamp::value( int index ) const
345 {
346  if ( mColors.empty() )
347  return 0;
348  return static_cast< double >( index ) / ( mColors.size() - 1 );
349 }
350 
351 QColor QgsLimitedRandomColorRamp::color( double value ) const
352 {
353  if ( value < 0 || value > 1 )
354  return QColor();
355 
356  int colorCnt = mColors.count();
357  int colorIdx = std::min( static_cast< int >( value * colorCnt ), colorCnt - 1 );
358 
359  if ( colorIdx >= 0 && colorIdx < colorCnt )
360  return mColors.at( colorIdx );
361 
362  return QColor();
363 }
364 
366 {
368 }
369 
371 {
373 }
374 
376 {
377  QVariantMap map;
378  map[QStringLiteral( "count" )] = QString::number( mCount );
379  map[QStringLiteral( "hueMin" )] = QString::number( mHueMin );
380  map[QStringLiteral( "hueMax" )] = QString::number( mHueMax );
381  map[QStringLiteral( "satMin" )] = QString::number( mSatMin );
382  map[QStringLiteral( "satMax" )] = QString::number( mSatMax );
383  map[QStringLiteral( "valMin" )] = QString::number( mValMin );
384  map[QStringLiteral( "valMax" )] = QString::number( mValMax );
385  map[QStringLiteral( "rampType" )] = type();
386  return map;
387 }
388 
390  int hueMax, int hueMin, int satMax, int satMin, int valMax, int valMin )
391 {
392  int h, s, v;
393  QList<QColor> colors;
394 
395  //normalize values
396  int safeHueMax = std::max( hueMin, hueMax );
397  int safeHueMin = std::min( hueMin, hueMax );
398  int safeSatMax = std::max( satMin, satMax );
399  int safeSatMin = std::min( satMin, satMax );
400  int safeValMax = std::max( valMin, valMax );
401  int safeValMin = std::min( valMin, valMax );
402 
403  //start hue at random angle
404  double currentHueAngle = 360.0 * static_cast< double >( qrand() ) / RAND_MAX;
405 
406  colors.reserve( count );
407  for ( int i = 0; i < count; ++i )
408  {
409  //increment hue by golden ratio (approx 137.507 degrees)
410  //as this minimizes hue nearness as count increases
411  //see http://basecase.org/env/on-rainbows for more details
412  currentHueAngle += 137.50776;
413  //scale hue to between hueMax and hueMin
414  h = qBound( 0.0, std::round( ( std::fmod( currentHueAngle, 360.0 ) / 360.0 ) * ( safeHueMax - safeHueMin ) + safeHueMin ), 359.0 );
415  s = qBound( 0, ( qrand() % ( safeSatMax - safeSatMin + 1 ) ) + safeSatMin, 255 );
416  v = qBound( 0, ( qrand() % ( safeValMax - safeValMin + 1 ) ) + safeValMin, 255 );
417  colors.append( QColor::fromHsv( h, s, v ) );
418  }
419  return colors;
420 }
421 
423 {
425 }
426 
428 
430 {
431  return -1;
432 }
433 
434 double QgsRandomColorRamp::value( int index ) const
435 {
436  Q_UNUSED( index )
437  return 0.0;
438 }
439 
440 QColor QgsRandomColorRamp::color( double value ) const
441 {
442  int minVal = 130;
443  int maxVal = 255;
444 
445  //if value is nan, then use last precalculated color
446  int colorIndex = ( !std::isnan( value ) ? value : 1 ) * ( mTotalColorCount - 1 );
447  if ( mTotalColorCount >= 1 && mPrecalculatedColors.length() > colorIndex )
448  {
449  //use precalculated hue
450  return mPrecalculatedColors.at( colorIndex );
451  }
452 
453  //can't use precalculated hues, use a totally random hue
454  int h = static_cast< int >( 360.0 * qrand() / ( RAND_MAX + 1.0 ) );
455  int s = ( qrand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
456  int v = ( qrand() % ( maxVal - minVal + 1 ) ) + minVal;
457  return QColor::fromHsv( h, s, v );
458 }
459 
460 void QgsRandomColorRamp::setTotalColorCount( const int colorCount )
461 {
462  //calculate colors in advance, so that we can ensure they are more visually distinct than pure random colors
463  mPrecalculatedColors.clear();
464  mTotalColorCount = colorCount;
465 
466  //This works OK for low color counts, but for > 10 or so colors there's still a good chance of
467  //similar colors being picked. TODO - investigate alternative "n-visually distinct color" routines
468 
469  //random offsets
470  double hueOffset = ( 360.0 * qrand() / ( RAND_MAX + 1.0 ) );
471 
472  //try to maximise difference between hues. this is not an ideal implementation, as constant steps
473  //through the hue wheel are not visually perceived as constant changes in hue
474  //(for instance, we are much more likely to get green hues than yellow hues)
475  double hueStep = 359.0 / colorCount;
476  double currentHue = hueOffset;
477 
478  //build up a list of colors
479  for ( int idx = 0; idx < colorCount; ++ idx )
480  {
481  int h = static_cast< int >( std::round( currentHue ) ) % 360;
482  int s = ( qrand() % ( DEFAULT_RANDOM_SAT_MAX - DEFAULT_RANDOM_SAT_MIN + 1 ) ) + DEFAULT_RANDOM_SAT_MIN;
483  int v = ( qrand() % ( DEFAULT_RANDOM_VAL_MAX - DEFAULT_RANDOM_VAL_MIN + 1 ) ) + DEFAULT_RANDOM_VAL_MIN;
484  mPrecalculatedColors << QColor::fromHsv( h, s, v );
485  currentHue += hueStep;
486  }
487 
488  //lastly, shuffle color list
489  std::random_shuffle( mPrecalculatedColors.begin(), mPrecalculatedColors.end() );
490 }
491 
493 {
495 }
496 
498 {
499  return new QgsRandomColorRamp();
500 }
501 
503 {
504  return QVariantMap();
505 }
506 
508 
509 QgsColorBrewerColorRamp::QgsColorBrewerColorRamp( const QString &schemeName, int colors, bool inverted )
510  : mSchemeName( schemeName )
511  , mColors( colors )
512  , mInverted( inverted )
513 {
514  loadPalette();
515 }
516 
517 QgsColorRamp *QgsColorBrewerColorRamp::create( const QVariantMap &props )
518 {
521  bool inverted = false;
522 
523  if ( props.contains( QStringLiteral( "schemeName" ) ) )
524  schemeName = props[QStringLiteral( "schemeName" )].toString();
525  if ( props.contains( QStringLiteral( "colors" ) ) )
526  colors = props[QStringLiteral( "colors" )].toInt();
527  if ( props.contains( QStringLiteral( "inverted" ) ) )
528  inverted = props[QStringLiteral( "inverted" )].toInt();
529 
530  return new QgsColorBrewerColorRamp( schemeName, colors, inverted );
531 }
532 
534 {
536 
537  if ( mInverted )
538  {
539  QList<QColor> tmpPalette;
540 
541  for ( int k = mPalette.size() - 1; k >= 0; k-- )
542  {
543  tmpPalette << mPalette.at( k );
544  }
545  mPalette = tmpPalette;
546  }
547 }
548 
550 {
552 }
553 
554 QList<int> QgsColorBrewerColorRamp::listSchemeVariants( const QString &schemeName )
555 {
557 }
558 
559 double QgsColorBrewerColorRamp::value( int index ) const
560 {
561  if ( mPalette.empty() )
562  return 0;
563  return static_cast< double >( index ) / ( mPalette.size() - 1 );
564 }
565 
566 QColor QgsColorBrewerColorRamp::color( double value ) const
567 {
568  if ( mPalette.isEmpty() || value < 0 || value > 1 || std::isnan( value ) )
569  return QColor();
570 
571  int paletteEntry = static_cast< int >( value * mPalette.count() );
572  if ( paletteEntry >= mPalette.count() )
573  paletteEntry = mPalette.count() - 1;
574  return mPalette.at( paletteEntry );
575 }
576 
578 {
579  mInverted = !mInverted;
580  loadPalette();
581 }
582 
584 {
586 }
587 
589 {
590  QVariantMap map;
591  map[QStringLiteral( "schemeName" )] = mSchemeName;
592  map[QStringLiteral( "colors" )] = QString::number( mColors );
593  map[QStringLiteral( "inverted" )] = QString::number( mInverted );
594  map[QStringLiteral( "rampType" )] = type();
595  return map;
596 }
597 
598 
600 
601 
602 QgsCptCityColorRamp::QgsCptCityColorRamp( const QString &schemeName, const QString &variantName,
603  bool inverted, bool doLoadFile )
605  , mSchemeName( schemeName )
606  , mVariantName( variantName )
607  , mInverted( inverted )
608 {
609  // TODO replace this with hard-coded data in the default case
610  // don't load file if variant is missing
611  if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
612  loadFile();
613 }
614 
615 QgsCptCityColorRamp::QgsCptCityColorRamp( const QString &schemeName, const QStringList &variantList,
616  const QString &variantName, bool inverted, bool doLoadFile )
618  , mSchemeName( schemeName )
619  , mVariantName( variantName )
620  , mVariantList( variantList )
621  , mInverted( inverted )
622 {
624 
625  // TODO replace this with hard-coded data in the default case
626  // don't load file if variant is missing
627  if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
628  loadFile();
629 }
630 
631 QgsColorRamp *QgsCptCityColorRamp::create( const QVariantMap &props )
632 {
635  bool inverted = false;
636 
637  if ( props.contains( QStringLiteral( "schemeName" ) ) )
638  schemeName = props[QStringLiteral( "schemeName" )].toString();
639  if ( props.contains( QStringLiteral( "variantName" ) ) )
640  variantName = props[QStringLiteral( "variantName" )].toString();
641  if ( props.contains( QStringLiteral( "inverted" ) ) )
642  inverted = props[QStringLiteral( "inverted" )].toInt();
643 
644  return new QgsCptCityColorRamp( schemeName, variantName, inverted );
645 }
646 
648 {
650 }
651 
653 {
654  mInverted = !mInverted;
656 }
657 
659 {
660  QgsCptCityColorRamp *ramp = new QgsCptCityColorRamp( QString(), QString(), mInverted, false );
661  ramp->copy( this );
662  return ramp;
663 }
664 
666 {
667  if ( ! other )
668  return;
669  mColor1 = other->color1();
670  mColor2 = other->color2();
671  mDiscrete = other->isDiscrete();
672  mStops = other->stops();
673  mSchemeName = other->mSchemeName;
674  mVariantName = other->mVariantName;
675  mVariantList = other->mVariantList;
676  mFileLoaded = other->mFileLoaded;
677  mInverted = other->mInverted;
678 }
679 
681 {
682  QgsGradientColorRamp *ramp =
684  // add author and copyright information
685  // TODO also add COPYING.xml file/link?
687  info[QStringLiteral( "cpt-city-gradient" )] = "<cpt-city>/" + mSchemeName + mVariantName + ".svg";
688  QString copyingFilename = copyingFileName();
689  copyingFilename.remove( QgsCptCityArchive::defaultBaseDir() );
690  info[QStringLiteral( "cpt-city-license" )] = "<cpt-city>" + copyingFilename;
691  ramp->setInfo( info );
692  return ramp;
693 }
694 
695 
697 {
698  QVariantMap map;
699  map[QStringLiteral( "schemeName" )] = mSchemeName;
700  map[QStringLiteral( "variantName" )] = mVariantName;
701  map[QStringLiteral( "inverted" )] = QString::number( mInverted );
702  map[QStringLiteral( "rampType" )] = type();
703  return map;
704 }
705 
706 
708 {
709  if ( mSchemeName.isEmpty() )
710  return QString();
711  else
712  {
713  return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mSchemeName + mVariantName + ".svg";
714  }
715 }
716 
718 {
719  return QgsCptCityArchive::findFileName( QStringLiteral( "COPYING.xml" ), QFileInfo( fileName() ).dir().path(),
721 }
722 
724 {
725  return QgsCptCityArchive::findFileName( QStringLiteral( "DESC.xml" ), QFileInfo( fileName() ).dir().path(),
727 }
728 
730 {
732 }
733 
735 {
736  if ( mFileLoaded )
737  {
738  QgsDebugMsg( "File already loaded for " + mSchemeName + mVariantName );
739  return true;
740  }
741 
742  // get filename
743  QString filename = fileName();
744  if ( filename.isNull() )
745  {
746  QgsDebugMsg( "Couldn't get fileName() for " + mSchemeName + mVariantName );
747  return false;
748  }
749 
750  QgsDebugMsg( QStringLiteral( "filename= %1 loaded=%2" ).arg( filename ).arg( mFileLoaded ) );
751 
752  // get color ramp from svg file
753  QMap< double, QPair<QColor, QColor> > colorMap =
755 
756  // add colors to palette
757  mFileLoaded = false;
758  mStops.clear();
759  QMap<double, QPair<QColor, QColor> >::const_iterator it, prev;
760  // first detect if file is gradient is continuous or discrete
761  // discrete: stop contains 2 colors and first color is identical to previous second
762  // multi: stop contains 2 colors and no relation with previous stop
763  mDiscrete = false;
764  mMultiStops = false;
765  it = prev = colorMap.constBegin();
766  while ( it != colorMap.constEnd() )
767  {
768  // look for stops that contain multiple values
769  if ( it != colorMap.constBegin() && ( it.value().first != it.value().second ) )
770  {
771  if ( it.value().first == prev.value().second )
772  {
773  mDiscrete = true;
774  break;
775  }
776  else
777  {
778  mMultiStops = true;
779  break;
780  }
781  }
782  prev = it;
783  ++it;
784  }
785 
786  // fill all stops
787  it = prev = colorMap.constBegin();
788  while ( it != colorMap.constEnd() )
789  {
790  if ( mDiscrete )
791  {
792  // mPalette << qMakePair( it.key(), it.value().second );
793  mStops.append( QgsGradientStop( it.key(), it.value().second ) );
794  }
795  else
796  {
797  // mPalette << qMakePair( it.key(), it.value().first );
798  mStops.append( QgsGradientStop( it.key(), it.value().first ) );
799  if ( ( mMultiStops ) &&
800  ( it.key() != 0.0 && it.key() != 1.0 ) )
801  {
802  mStops.append( QgsGradientStop( it.key(), it.value().second ) );
803  }
804  }
805  prev = it;
806  ++it;
807  }
808 
809  // remove first and last items (mColor1 and mColor2)
810  if ( ! mStops.isEmpty() && mStops.at( 0 ).offset == 0.0 )
811  mColor1 = mStops.takeFirst().color;
812  if ( ! mStops.isEmpty() && mStops.last().offset == 1.0 )
813  mColor2 = mStops.takeLast().color;
814 
815  if ( mInverted )
816  {
818  }
819 
820  mFileLoaded = true;
821  return true;
822 }
823 
824 
825 //
826 // QgsPresetColorRamp
827 //
828 
830 {
831  const auto constColors = colors;
832  for ( const QColor &color : constColors )
833  {
834  mColors << qMakePair( color, color.name() );
835  }
836  // need at least one color
837  if ( mColors.isEmpty() )
838  mColors << qMakePair( QColor( 250, 75, 60 ), QStringLiteral( "#fa4b3c" ) );
839 }
840 
842  : mColors( colors )
843 {
844  // need at least one color
845  if ( mColors.isEmpty() )
846  mColors << qMakePair( QColor( 250, 75, 60 ), QStringLiteral( "#fa4b3c" ) );
847 }
848 
849 QgsColorRamp *QgsPresetSchemeColorRamp::create( const QVariantMap &properties )
850 {
852 
853  int i = 0;
854  QString colorString = properties.value( QStringLiteral( "preset_color_%1" ).arg( i ), QString() ).toString();
855  QString colorName = properties.value( QStringLiteral( "preset_color_name_%1" ).arg( i ), QString() ).toString();
856  while ( !colorString.isEmpty() )
857  {
858  colors << qMakePair( QgsSymbolLayerUtils::decodeColor( colorString ), colorName );
859  i++;
860  colorString = properties.value( QStringLiteral( "preset_color_%1" ).arg( i ), QString() ).toString();
861  colorName = properties.value( QStringLiteral( "preset_color_name_%1" ).arg( i ), QString() ).toString();
862  }
863 
864  return new QgsPresetSchemeColorRamp( colors );
865 }
866 
867 QList<QColor> QgsPresetSchemeColorRamp::colors() const
868 {
869  QList< QColor > l;
870  l.reserve( mColors.count() );
871  for ( int i = 0; i < mColors.count(); ++i )
872  {
873  l << mColors.at( i ).first;
874  }
875  return l;
876 }
877 
878 double QgsPresetSchemeColorRamp::value( int index ) const
879 {
880  if ( mColors.empty() )
881  return 0;
882  return static_cast< double >( index ) / ( mColors.size() - 1 );
883 }
884 
885 QColor QgsPresetSchemeColorRamp::color( double value ) const
886 {
887  if ( value < 0 || value > 1 )
888  return QColor();
889 
890  int colorCnt = mColors.count();
891  int colorIdx = std::min( static_cast< int >( value * colorCnt ), colorCnt - 1 );
892 
893  if ( colorIdx >= 0 && colorIdx < colorCnt )
894  return mColors.at( colorIdx ).first;
895 
896  return QColor();
897 }
898 
900 {
902 }
903 
905 {
906  QgsNamedColorList tmpColors;
907 
908  for ( int k = mColors.size() - 1; k >= 0; k-- )
909  {
910  tmpColors << mColors.at( k );
911  }
912  mColors = tmpColors;
913 }
914 
916 {
917  return new QgsPresetSchemeColorRamp( *this );
918 }
919 
921 {
922  QVariantMap props;
923  for ( int i = 0; i < mColors.count(); ++i )
924  {
925  props.insert( QStringLiteral( "preset_color_%1" ).arg( i ), QgsSymbolLayerUtils::encodeColor( mColors.at( i ).first ) );
926  props.insert( QStringLiteral( "preset_color_name_%1" ).arg( i ), mColors.at( i ).second );
927  }
928  props[QStringLiteral( "rampType" )] = type();
929  return props;
930 }
931 
933 {
934  return mColors.count();
935 }
936 
938 {
939  return mColors;
940 }
941 
942 QList<QPair<QString, QString> > QgsColorRamp::rampTypes()
943 {
944  return QList<QPair<QString, QString> >
945  {
946  qMakePair( QgsGradientColorRamp::typeString(), QObject::tr( "Gradient" ) ),
947  qMakePair( QgsPresetSchemeColorRamp::typeString(), QObject::tr( "Color Presets" ) ),
948  qMakePair( QgsLimitedRandomColorRamp::typeString(), QObject::tr( "Random" ) ),
949  qMakePair( QgsCptCityColorRamp::typeString(), QObject::tr( "Catalog: cpt-city" ) ),
950  qMakePair( QgsColorBrewerColorRamp::typeString(), QObject::tr( "Catalog: ColorBrewer" ) )
951  };
952 }
Color ramp utilising "Color Brewer" preset color schemes.
Definition: qgscolorramp.h:578
QList< QColor > mPalette
Definition: qgscolorramp.h:664
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 QString typeString()
Returns the string identifier for QgsColorBrewerColorRamp.
Definition: qgscolorramp.h:607
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.
Definition: qgscolorramp.h:609
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.
Definition: qgscolorramp.h:619
int colors() const
Returns the number of colors in the ramp.
Definition: qgscolorramp.h:625
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.
Definition: qgscolorramp.h:32
static QList< QPair< QString, QString > > rampTypes()
Returns a list of available ramp types, where the first value in each item is the QgsColorRamp::type(...
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
QStringList mVariantList
Definition: qgscolorramp.h:749
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
Definition: qgscolorramp.h:726
QString fileName() const
void copy(const QgsCptCityColorRamp *other)
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
Definition: qgscolorramp.h:712
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
Definition: qgscolorramp.h:724
QString variantName() const
Definition: qgscolorramp.h:725
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:151
QgsGradientStopsList mStops
Definition: qgscolorramp.h:282
void setInfo(const QgsStringMap &info)
Sets additional info to attach to the gradient ramp (e.g., authorship notes)
Definition: qgscolorramp.h:267
QgsStringMap mInfo
Definition: qgscolorramp.h:283
bool isDiscrete() const
Returns true if the gradient is using discrete interpolation, rather than smoothly interpolating betw...
Definition: qgscolorramp.h:221
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)
Definition: qgscolorramp.h:260
void convertToDiscrete(bool discrete)
Converts a gradient with existing color stops to or from discrete interpolation.
QColor color(double value) const override
Returns the color corresponding to a specified value.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
Definition: qgscolorramp.h:179
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.
Definition: qgscolorramp.h:191
void invert() override
Inverts the ordering of the color ramp.
QgsGradientStopsList stops() const
Returns the list of intermediate gradient stops for the ramp.
Definition: qgscolorramp.h:254
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
void addStopsToGradient(QGradient *gradient, double opacity=1)
Copy color ramp stops to a QGradient.
QgsGradientColorRamp * clone() const override
Creates a clone of the color ramp.
QColor color2() const
Returns the gradient end color.
Definition: qgscolorramp.h:198
Represents a color stop within a QgsGradientColorRamp color ramp.
Definition: qgscolorramp.h:113
double offset
Relative positional offset, between 0 and 1.
Definition: qgscolorramp.h:127
Constrained random color ramp, which returns random colors based on preset parameters.
Definition: qgscolorramp.h:303
static QString typeString()
Returns the string identifier for QgsLimitedRandomColorRamp.
Definition: qgscolorramp.h:337
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.
Definition: qgscolorramp.h:342
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.
Definition: qgscolorramp.h:393
QString type() const override
Returns a string representing the color ramp type.
int satMax() const
Returns the maximum saturation for generated colors.
Definition: qgscolorramp.h:381
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.
Definition: qgscolorramp.h:369
int hueMin() const
Returns the minimum hue for generated colors.
Definition: qgscolorramp.h:363
QList< QColor > mColors
Definition: qgscolorramp.h:444
int valMin() const
Returns the minimum value for generated colors.
Definition: qgscolorramp.h:387
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.
Definition: qgscolorramp.h:375
A scheme based color ramp consisting of a list of predefined colors.
Definition: qgscolorramp.h:506
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.
Definition: qgscolorramp.h:550
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.
Totally random color ramp.
Definition: qgscolorramp.h:455
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
QList< QColor > mPrecalculatedColors
Definition: qgscolorramp.h:494
QgsRandomColorRamp * clone() const override
Creates a clone of the color ramp.
QgsRandomColorRamp()=default
Constructor for QgsRandomColorRamp.
static QString typeString()
Returns the string identifier for QgsRandomColorRamp.
Definition: qgscolorramp.h:483
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.
static QColor decodeColor(const QString &str)
static QString encodeColor(const QColor &color)
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
int ANALYSIS_EXPORT lower(int n, int i)
Lower function.
Definition: MathUtils.cpp:407
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:316
QMap< QString, QString > QgsStringMap
Definition: qgis.h:759
bool stopLessThan(const QgsGradientStop &s1, const QgsGradientStop &s2)
#define DEFAULT_COLORBREWER_COLORS
Definition: qgscolorramp.h:569
#define DEFAULT_COLORBREWER_SCHEMENAME
Definition: qgscolorramp.h:568
#define DEFAULT_RANDOM_HUE_MAX
Definition: qgscolorramp.h:290
#define DEFAULT_CPTCITY_SCHEMENAME
Definition: qgscolorramp.h:669
#define DEFAULT_RANDOM_HUE_MIN
Definition: qgscolorramp.h:289
#define DEFAULT_RANDOM_COUNT
Definition: qgscolorramp.h:288
#define DEFAULT_RANDOM_SAT_MAX
Definition: qgscolorramp.h:294
#define DEFAULT_RANDOM_SAT_MIN
Definition: qgscolorramp.h:293
#define DEFAULT_CPTCITY_VARIANTNAME
Definition: qgscolorramp.h:670
#define DEFAULT_GRADIENT_COLOR1
Definition: qgscolorramp.h:140
#define DEFAULT_RANDOM_VAL_MIN
Definition: qgscolorramp.h:291
QList< QgsGradientStop > QgsGradientStopsList
List of gradient stops.
Definition: qgscolorramp.h:138
#define DEFAULT_GRADIENT_COLOR2
Definition: qgscolorramp.h:141
#define DEFAULT_RANDOM_VAL_MAX
Definition: qgscolorramp.h:292
#define QgsDebugMsg(str)
Definition: qgslogger.h:38