QGIS API Documentation  2.99.0-Master (d55fa22)
qgscolorrampshader.cpp
Go to the documentation of this file.
1 /* **************************************************************************
2  qgscolorrampshader.cpp - description
3  -------------------
4 begin : Fri Dec 28 2007
5 copyright : (C) 2007 by Peter J. Ersts
6 email : [email protected]
7 
8 This class is based off of code that was originally written by Marco Hugentobler and
9 originally part of the larger QgsRasterLayer class
10 ****************************************************************************/
11 
12 /* **************************************************************************
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * *
19  ***************************************************************************/
20 
21 // Threshold for treating values as exact match.
22 // Set to 0.0 to support displaying small values (https://issues.qgis.org/issues/12581)
23 #define DOUBLE_DIFF_THRESHOLD 0.0 // 0.0000001
24 
25 #include "qgslogger.h"
26 #include "qgis.h"
27 #include "qgscolorramp.h"
28 #include "qgscolorrampshader.h"
29 #include "qgsrasterinterface.h"
30 #include "qgsrasterminmaxorigin.h"
31 
32 #include <cmath>
33 QgsColorRampShader::QgsColorRampShader( double minimumValue, double maximumValue, QgsColorRamp *colorRamp, Type type, ClassificationMode classificationMode )
34  : QgsRasterShaderFunction( minimumValue, maximumValue )
35  , mColorRampType( type )
36  , mClassificationMode( classificationMode )
37  , mLUTOffset( 0.0 )
38  , mLUTFactor( 1.0 )
39  , mLUTInitialized( false )
40  , mClip( false )
41 {
42  QgsDebugMsgLevel( "called.", 4 );
43 
44  setSourceColorRamp( colorRamp );
45 }
46 
48  : QgsRasterShaderFunction( other )
49  , mColorRampType( other.mColorRampType )
50  , mClassificationMode( other.mClassificationMode )
51  , mLUT( other.mLUT )
52  , mLUTOffset( other.mLUTOffset )
53  , mLUTFactor( other.mLUTFactor )
54  , mLUTInitialized( other.mLUTInitialized )
55  , mClip( other.mClip )
56 {
57  mSourceColorRamp.reset( other.sourceColorRamp()->clone() );
58 }
59 
61 {
62  mSourceColorRamp.reset( other.sourceColorRamp()->clone() );
63  mColorRampType = other.mColorRampType;
64  mClassificationMode = other.mClassificationMode;
65  mLUT = other.mLUT;
66  mLUTOffset = other.mLUTOffset;
67  mLUTFactor = other.mLUTFactor;
68  mLUTInitialized = other.mLUTInitialized;
69  mClip = other.mClip;
70  return *this;
71 }
72 
74 {
75  switch ( mColorRampType )
76  {
77  case Interpolated:
78  return QStringLiteral( "INTERPOLATED" );
79  case Discrete:
80  return QStringLiteral( "DISCRETE" );
81  case Exact:
82  return QStringLiteral( "EXACT" );
83  }
84  return QStringLiteral( "Unknown" );
85 }
86 
87 void QgsColorRampShader::setColorRampItemList( const QList<QgsColorRampShader::ColorRampItem> &list )
88 {
89  mColorRampItemList = list.toVector();
90  // Reset the look up table when the color ramp is changed
91  mLUTInitialized = false;
92  mLUT.clear();
93 }
94 
96 {
97  mColorRampType = colorRampType;
98 }
99 
100 void QgsColorRampShader::setColorRampType( const QString &type )
101 {
102  if ( type == QLatin1String( "INTERPOLATED" ) )
103  {
104  mColorRampType = Interpolated;
105  }
106  else if ( type == QLatin1String( "DISCRETE" ) )
107  {
108  mColorRampType = Discrete;
109  }
110  else
111  {
112  mColorRampType = Exact;
113  }
114 }
115 
117 {
118  return mSourceColorRamp.get();
119 }
120 
122 {
123  mSourceColorRamp.reset( colorramp );
124 }
125 
126 void QgsColorRampShader::classifyColorRamp( const int classes, const int band, const QgsRectangle &extent, QgsRasterInterface *input )
127 {
128  if ( minimumValue() >= maximumValue() )
129  {
130  return;
131  }
132 
133  bool discrete = colorRampType() == Discrete;
134 
135  QList<double> entryValues;
136  QVector<QColor> entryColors;
137 
138  double min = minimumValue();
139  double max = maximumValue();
140 
141  if ( classificationMode() == Continuous )
142  {
143  if ( sourceColorRamp() && sourceColorRamp()->count() > 1 )
144  {
145  int numberOfEntries = sourceColorRamp()->count();
146  entryValues.reserve( numberOfEntries );
147  if ( discrete )
148  {
149  double intervalDiff = max - min;
150 
151  // remove last class when ColorRamp is gradient and discrete, as they are implemented with an extra stop
152  QgsGradientColorRamp *colorGradientRamp = dynamic_cast<QgsGradientColorRamp *>( sourceColorRamp() );
153  if ( colorGradientRamp && colorGradientRamp->isDiscrete() )
154  {
155  numberOfEntries--;
156  }
157  else
158  {
159  // if color ramp is continuous scale values to get equally distributed classes.
160  // Doesn't work perfectly when stops are non equally distributed.
161  intervalDiff *= ( numberOfEntries - 1 ) / ( double )numberOfEntries;
162  }
163 
164  // skip first value (always 0.0)
165  for ( int i = 1; i < numberOfEntries; ++i )
166  {
167  double value = sourceColorRamp()->value( i );
168  entryValues.push_back( min + value * intervalDiff );
169  }
170  entryValues.push_back( std::numeric_limits<double>::infinity() );
171  }
172  else
173  {
174  for ( int i = 0; i < numberOfEntries; ++i )
175  {
176  double value = sourceColorRamp()->value( i );
177  entryValues.push_back( min + value * ( max - min ) );
178  }
179  }
180  // for continuous mode take original color map colors
181  for ( int i = 0; i < numberOfEntries; ++i )
182  {
183  int idx = i;
184  entryColors.push_back( sourceColorRamp()->color( sourceColorRamp()->value( idx ) ) );
185  }
186  }
187  }
188  else // for other classification modes interpolate colors linearly
189  {
190  if ( classes < 2 )
191  return; // < 2 classes is not useful, shouldn't happen, but if it happens save it from crashing
192 
193  if ( classificationMode() == Quantile )
194  {
195  // Quantile
196  if ( band < 0 || !input )
197  return; // quantile classificationr requires a valid band, minMaxOrigin, and input
198 
199  double cut1 = std::numeric_limits<double>::quiet_NaN();
200  double cut2 = std::numeric_limits<double>::quiet_NaN();
201  int sampleSize = 250000;
202 
203  // set min and max from histogram, used later to calculate number of decimals to display
204  input->cumulativeCut( band, 0.0, 1.0, min, max, extent, sampleSize );
205 
206  entryValues.reserve( classes );
207  if ( discrete )
208  {
209  double intervalDiff = 1.0 / ( classes );
210  for ( int i = 1; i < classes; ++i )
211  {
212  input->cumulativeCut( band, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
213  entryValues.push_back( cut2 );
214  }
215  entryValues.push_back( std::numeric_limits<double>::infinity() );
216  }
217  else
218  {
219  double intervalDiff = 1.0 / ( classes - 1 );
220  for ( int i = 0; i < classes; ++i )
221  {
222  input->cumulativeCut( band, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
223  entryValues.push_back( cut2 );
224  }
225  }
226  }
227  else // EqualInterval
228  {
229  entryValues.reserve( classes );
230  if ( discrete )
231  {
232  // in discrete mode the lowest value is not an entry and the highest
233  // value is inf, there are ( numberOfEntries ) of which the first
234  // and last are not used.
235  double intervalDiff = ( max - min ) / ( classes );
236 
237  for ( int i = 1; i < classes; ++i )
238  {
239  entryValues.push_back( min + i * intervalDiff );
240  }
241  entryValues.push_back( std::numeric_limits<double>::infinity() );
242  }
243  else
244  {
245  //because the highest value is also an entry, there are (numberOfEntries - 1) intervals
246  double intervalDiff = ( max - min ) / ( classes - 1 );
247 
248  for ( int i = 0; i < classes; ++i )
249  {
250  entryValues.push_back( min + i * intervalDiff );
251  }
252  }
253  }
254 
255  if ( !sourceColorRamp() || sourceColorRamp()->count() == 1 )
256  {
257  //hard code color range from blue -> red (previous default)
258  int colorDiff = 0;
259  if ( classes != 0 )
260  {
261  colorDiff = ( int )( 255 / classes );
262  }
263 
264  entryColors.reserve( classes );
265  for ( int i = 0; i < classes; ++i )
266  {
267  QColor currentColor;
268  int idx = i;
269  currentColor.setRgb( colorDiff * idx, 0, 255 - colorDiff * idx );
270  entryColors.push_back( currentColor );
271  }
272  }
273  else
274  {
275  entryColors.reserve( classes );
276  for ( int i = 0; i < classes; ++i )
277  {
278  int idx = i;
279  entryColors.push_back( sourceColorRamp()->color( ( ( double ) idx ) / ( classes - 1 ) ) );
280  }
281  }
282  }
283 
284  QList<double>::const_iterator value_it = entryValues.begin();
285  QVector<QColor>::const_iterator color_it = entryColors.begin();
286 
287  // calculate a reasonable number of decimals to display
288  double maxabs = log10( qMax( qAbs( max ), qAbs( min ) ) );
289  int nDecimals = qRound( qMax( 3.0 + maxabs - log10( max - min ), maxabs <= 15.0 ? maxabs + 0.49 : 0.0 ) );
290 
291  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
292  for ( ; value_it != entryValues.end(); ++value_it, ++color_it )
293  {
294  QgsColorRampShader::ColorRampItem newColorRampItem;
295  newColorRampItem.value = *value_it;
296  newColorRampItem.color = *color_it;
297  newColorRampItem.label = QString::number( *value_it, 'g', nDecimals );
298  colorRampItems.append( newColorRampItem );
299  }
300 
301  std::sort( colorRampItems.begin(), colorRampItems.end() );
302  setColorRampItemList( colorRampItems );
303 }
304 
305 void QgsColorRampShader::classifyColorRamp( const int band, const QgsRectangle &extent, QgsRasterInterface *input )
306 {
307  classifyColorRamp( colorRampItemList().count(), band, extent, input );
308 }
309 
310 bool QgsColorRampShader::shade( double value, int *returnRedValue, int *returnGreenValue, int *returnBlueValue, int *returnAlphaValue )
311 {
312  if ( mColorRampItemList.isEmpty() )
313  {
314  return false;
315  }
316  if ( qIsNaN( value ) || qIsInf( value ) )
317  return false;
318 
319  int colorRampItemListCount = mColorRampItemList.count();
320  int idx;
321  if ( !mLUTInitialized )
322  {
323  // calculate LUT for faster index recovery
324  mLUTFactor = 1.0;
325  double minimumValue = mColorRampItemList.first().value;
326  mLUTOffset = minimumValue + DOUBLE_DIFF_THRESHOLD;
327  // Only make lut if at least 3 items, with 2 items the low and high cases handle both
328  if ( colorRampItemListCount >= 3 )
329  {
330  double rangeValue = mColorRampItemList.at( colorRampItemListCount - 2 ).value - minimumValue;
331  if ( rangeValue > 0 )
332  {
333  int lutSize = 256; // TODO: test if speed can be increased with a different LUT size
334  mLUTFactor = ( lutSize - 0.0000001 ) / rangeValue; // decrease slightly to make sure last LUT category is correct
335  idx = 0;
336  double val;
337  mLUT.reserve( lutSize );
338  for ( int i = 0; i < lutSize; i++ )
339  {
340  val = ( i / mLUTFactor ) + mLUTOffset;
341  while ( idx < colorRampItemListCount
342  && mColorRampItemList.at( idx ).value - DOUBLE_DIFF_THRESHOLD < val )
343  {
344  idx++;
345  }
346  mLUT.push_back( idx );
347  }
348  }
349  }
350  mLUTInitialized = true;
351  }
352 
353  // overflow indicates that value > maximum value + DOUBLE_DIFF_THRESHOLD
354  // that way idx can point to the last valid item
355  bool overflow = false;
356 
357  // find index of the first ColorRampItem that is equal or higher to theValue
358  int lutIndex = ( value - mLUTOffset ) * mLUTFactor;
359  if ( value < mLUTOffset )
360  {
361  idx = 0;
362  }
363  else if ( lutIndex >= mLUT.count() )
364  {
365  idx = colorRampItemListCount - 1;
366  if ( mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < value )
367  {
368  overflow = true;
369  }
370  }
371  else
372  {
373  // get initial value from LUT
374  idx = mLUT.at( lutIndex );
375 
376  // check if it's correct and if not increase until correct
377  // the LUT is made in such a way the index is always correct or too low, never too high
378  while ( idx < colorRampItemListCount && mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < value )
379  {
380  idx++;
381  }
382  if ( idx >= colorRampItemListCount )
383  {
384  idx = colorRampItemListCount - 1;
385  overflow = true;
386  }
387  }
388 
389  const QgsColorRampShader::ColorRampItem &currentColorRampItem = mColorRampItemList.at( idx );
390 
391  if ( colorRampType() == Interpolated )
392  {
393  // Interpolate the color between two class breaks linearly.
394  if ( idx < 1 || overflow || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD <= value )
395  {
396  if ( mClip && ( overflow
397  || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD > value ) )
398  {
399  return false;
400  }
401  *returnRedValue = currentColorRampItem.color.red();
402  *returnGreenValue = currentColorRampItem.color.green();
403  *returnBlueValue = currentColorRampItem.color.blue();
404  *returnAlphaValue = currentColorRampItem.color.alpha();
405  return true;
406  }
407 
408  const QgsColorRampShader::ColorRampItem &previousColorRampItem = mColorRampItemList.at( idx - 1 );
409 
410  double currentRampRange = currentColorRampItem.value - previousColorRampItem.value;
411  double offsetInRange = value - previousColorRampItem.value;
412  double scale = offsetInRange / currentRampRange;
413 
414  *returnRedValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.red() ) + ( static_cast< double >( currentColorRampItem.color.red() - previousColorRampItem.color.red() ) * scale ) );
415  *returnGreenValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.green() ) + ( static_cast< double >( currentColorRampItem.color.green() - previousColorRampItem.color.green() ) * scale ) );
416  *returnBlueValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.blue() ) + ( static_cast< double >( currentColorRampItem.color.blue() - previousColorRampItem.color.blue() ) * scale ) );
417  *returnAlphaValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.alpha() ) + ( static_cast< double >( currentColorRampItem.color.alpha() - previousColorRampItem.color.alpha() ) * scale ) );
418  return true;
419  }
420  else if ( colorRampType() == Discrete )
421  {
422  // Assign the color of the higher class for every pixel between two class breaks.
423  // NOTE: The implementation has always been different than the documentation,
424  // which said lower class before, see https://issues.qgis.org/issues/13995
425  if ( overflow )
426  {
427  return false;
428  }
429  *returnRedValue = currentColorRampItem.color.red();
430  *returnGreenValue = currentColorRampItem.color.green();
431  *returnBlueValue = currentColorRampItem.color.blue();
432  *returnAlphaValue = currentColorRampItem.color.alpha();
433  return true;
434  }
435  else // EXACT
436  {
437  // Assign the color of the exact matching value in the color ramp item list
438  if ( !overflow && currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD <= value )
439  {
440  *returnRedValue = currentColorRampItem.color.red();
441  *returnGreenValue = currentColorRampItem.color.green();
442  *returnBlueValue = currentColorRampItem.color.blue();
443  *returnAlphaValue = currentColorRampItem.color.alpha();
444  return true;
445  }
446  else
447  {
448  return false;
449  }
450  }
451 }
452 
453 bool QgsColorRampShader::shade( double redValue, double greenValue,
454  double blueValue, double alphaValue,
455  int *returnRedValue, int *returnGreenValue,
456  int *returnBlueValue, int *returnAlphaValue )
457 {
458  Q_UNUSED( redValue );
459  Q_UNUSED( greenValue );
460  Q_UNUSED( blueValue );
461  Q_UNUSED( alphaValue );
462 
463  *returnRedValue = 0;
464  *returnGreenValue = 0;
465  *returnBlueValue = 0;
466  *returnAlphaValue = 0;
467 
468  return false;
469 }
470 
471 void QgsColorRampShader::legendSymbologyItems( QList< QPair< QString, QColor > > &symbolItems ) const
472 {
473  QVector<QgsColorRampShader::ColorRampItem>::const_iterator colorRampIt = mColorRampItemList.constBegin();
474  for ( ; colorRampIt != mColorRampItemList.constEnd(); ++colorRampIt )
475  {
476  symbolItems.push_back( qMakePair( colorRampIt->label, colorRampIt->color ) );
477  }
478 }
A rectangle specified with double values.
Definition: qgsrectangle.h:38
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Set custom colormap.
Uses quantile (i.e. equal pixel) count.
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Get the custom colormap.
QgsColorRampShader & operator=(const QgsColorRampShader &other)
Assignment operator.
virtual void cumulativeCut(int bandNo, double lowerCount, double upperCount, double &lowerValue, double &upperValue, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0)
Find values for cumulative pixel count cut.
Type
Supported methods for color interpolation.
void setColorRampType(QgsColorRampShader::Type colorRampType)
Set the color ramp type.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:38
bool shade(double, int *, int *, int *, int *) override
Generates and new RGB value based on one input value.
The raster shade function applies a shader to a pixel at render time - typically used to render grays...
virtual double value(int index) const =0
Returns relative value between [0,1] of color at specified index.
void legendSymbologyItems(QList< QPair< QString, QColor > > &symbolItems) const override
Get symbology items if provided by renderer.
double ANALYSIS_EXPORT min(double x, double y)
Returns the minimum of two doubles or the first argument if both are equal.
Definition: MathUtils.cc:452
bool isDiscrete() const
Returns true if the gradient is using discrete interpolation, rather than smoothly interpolating betw...
Definition: qgscolorramp.h:185
virtual int count() const =0
Returns number of defined colors, or -1 if undefined.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
Base class for processing filters like renderers, reprojector, resampler etc.
double ANALYSIS_EXPORT max(double x, double y)
Returns the maximum of two doubles or the first argument if both are equal.
Definition: MathUtils.cc:437
QString colorRampTypeAsQString()
Get the color ramp type as a string.
Type colorRampType() const
Get the color ramp type.
Assigns the color of the exact matching value in the color ramp item list.
void classifyColorRamp(const int classes=0, const int band=-1, const QgsRectangle &extent=QgsRectangle(), QgsRasterInterface *input=nullptr)
Classify color ramp shader.
Uses breaks from color palette.
std::unique_ptr< QgsColorRamp > mSourceColorRamp
Source color ramp.
#define DOUBLE_DIFF_THRESHOLD
Interpolates the color between two class breaks linearly.
ClassificationMode
Classification modes used to create the color ramp shader.
Assigns the color of the higher class for every pixel between two class breaks.
ClassificationMode classificationMode() const
Returns the classification mode.
QgsColorRamp * sourceColorRamp() const
Get the source color ramp.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:128
QgsColorRampShader(double minimumValue=0.0, double maximumValue=255.0, QgsColorRamp *colorRamp=nullptr, Type type=Interpolated, ClassificationMode classificationMode=Continuous)
Creates a new color ramp shader.