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