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