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