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