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