QGIS API Documentation  2.99.0-Master (90ae728)
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 (http://hub.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  { // Quantile
195  if ( band < 0 || !input )
196  return; // quantile classificationr requires a valid band, minMaxOrigin, and input
197 
198  double cut1 = std::numeric_limits<double>::quiet_NaN();
199  double cut2 = std::numeric_limits<double>::quiet_NaN();
200  int sampleSize = 250000;
201 
202  // set min and max from histogram, used later to calculate number of decimals to display
203  input->cumulativeCut( band, 0.0, 1.0, min, max, extent, sampleSize );
204 
205  entryValues.reserve( classes );
206  if ( discrete )
207  {
208  double intervalDiff = 1.0 / ( classes );
209  for ( int i = 1; i < classes; ++i )
210  {
211  input->cumulativeCut( band, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
212  entryValues.push_back( cut2 );
213  }
214  entryValues.push_back( std::numeric_limits<double>::infinity() );
215  }
216  else
217  {
218  double intervalDiff = 1.0 / ( classes - 1 );
219  for ( int i = 0; i < classes; ++i )
220  {
221  input->cumulativeCut( band, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
222  entryValues.push_back( cut2 );
223  }
224  }
225  }
226  else // EqualInterval
227  {
228  entryValues.reserve( classes );
229  if ( discrete )
230  {
231  // in discrete mode the lowest value is not an entry and the highest
232  // value is inf, there are ( numberOfEntries ) of which the first
233  // and last are not used.
234  double intervalDiff = ( max - min ) / ( classes );
235 
236  for ( int i = 1; i < classes; ++i )
237  {
238  entryValues.push_back( min + i * intervalDiff );
239  }
240  entryValues.push_back( std::numeric_limits<double>::infinity() );
241  }
242  else
243  {
244  //because the highest value is also an entry, there are (numberOfEntries - 1) intervals
245  double intervalDiff = ( max - min ) / ( classes - 1 );
246 
247  for ( int i = 0; i < classes; ++i )
248  {
249  entryValues.push_back( min + i * intervalDiff );
250  }
251  }
252  }
253 
254  if ( !sourceColorRamp() || sourceColorRamp()->count() == 1 )
255  {
256  //hard code color range from blue -> red (previous default)
257  int colorDiff = 0;
258  if ( classes != 0 )
259  {
260  colorDiff = ( int )( 255 / classes );
261  }
262 
263  entryColors.reserve( classes );
264  for ( int i = 0; i < classes; ++i )
265  {
266  QColor currentColor;
267  int idx = i;
268  currentColor.setRgb( colorDiff*idx, 0, 255 - colorDiff * idx );
269  entryColors.push_back( currentColor );
270  }
271  }
272  else
273  {
274  entryColors.reserve( classes );
275  for ( int i = 0; i < classes; ++i )
276  {
277  int idx = i;
278  entryColors.push_back( sourceColorRamp()->color((( double ) idx ) / ( classes - 1 ) ) );
279  }
280  }
281  }
282 
283  QList<double>::const_iterator value_it = entryValues.begin();
284  QVector<QColor>::const_iterator color_it = entryColors.begin();
285 
286  // calculate a reasonable number of decimals to display
287  double maxabs = log10( qMax( qAbs( max ), qAbs( min ) ) );
288  int nDecimals = qRound( qMax( 3.0 + maxabs - log10( max - min ), maxabs <= 15.0 ? maxabs + 0.49 : 0.0 ) );
289 
290  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
291  for ( ; value_it != entryValues.end(); ++value_it, ++color_it )
292  {
293  QgsColorRampShader::ColorRampItem newColorRampItem;
294  newColorRampItem.value = *value_it;
295  newColorRampItem.color = *color_it;
296  newColorRampItem.label = QString::number( *value_it, 'g', nDecimals );
297  colorRampItems.append( newColorRampItem );
298  }
299 
300  std::sort( colorRampItems.begin(), colorRampItems.end() );
301  setColorRampItemList( colorRampItems );
302 }
303 
304 void QgsColorRampShader::classifyColorRamp( const int band, const QgsRectangle& extent, QgsRasterInterface* input )
305 {
306  classifyColorRamp( colorRampItemList().count(), band, extent, input );
307 }
308 
309 bool QgsColorRampShader::shade( double value, int* returnRedValue, int* returnGreenValue, int* returnBlueValue, int *returnAlphaValue )
310 {
311  if ( mColorRampItemList.isEmpty() )
312  {
313  return false;
314  }
315  if ( qIsNaN( value ) || qIsInf( value ) )
316  return false;
317 
318  int colorRampItemListCount = mColorRampItemList.count();
319  int idx;
320  if ( !mLUTInitialized )
321  {
322  // calculate LUT for faster index recovery
323  mLUTFactor = 1.0;
324  double minimumValue = mColorRampItemList.first().value;
325  mLUTOffset = minimumValue + DOUBLE_DIFF_THRESHOLD;
326  // Only make lut if at least 3 items, with 2 items the low and high cases handle both
327  if ( colorRampItemListCount >= 3 )
328  {
329  double rangeValue = mColorRampItemList.at( colorRampItemListCount - 2 ).value - minimumValue;
330  if ( rangeValue > 0 )
331  {
332  int lutSize = 256; // TODO: test if speed can be increased with a different LUT size
333  mLUTFactor = ( lutSize - 0.0000001 ) / rangeValue; // decrease slightly to make sure last LUT category is correct
334  idx = 0;
335  double val;
336  mLUT.reserve( lutSize );
337  for ( int i = 0; i < lutSize; i++ )
338  {
339  val = ( i / mLUTFactor ) + mLUTOffset;
340  while ( idx < colorRampItemListCount
341  && mColorRampItemList.at( idx ).value - DOUBLE_DIFF_THRESHOLD < val )
342  {
343  idx++;
344  }
345  mLUT.push_back( idx );
346  }
347  }
348  }
349  mLUTInitialized = true;
350  }
351 
352  // overflow indicates that value > maximum value + DOUBLE_DIFF_THRESHOLD
353  // that way idx can point to the last valid item
354  bool overflow = false;
355 
356  // find index of the first ColorRampItem that is equal or higher to theValue
357  int lutIndex = ( value - mLUTOffset ) * mLUTFactor;
358  if ( value < mLUTOffset )
359  {
360  idx = 0;
361  }
362  else if ( lutIndex >= mLUT.count() )
363  {
364  idx = colorRampItemListCount - 1;
365  if ( mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < value )
366  {
367  overflow = true;
368  }
369  }
370  else
371  {
372  // get initial value from LUT
373  idx = mLUT.at( lutIndex );
374 
375  // check if it's correct and if not increase until correct
376  // the LUT is made in such a way the index is always correct or too low, never too high
377  while ( idx < colorRampItemListCount && mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < value )
378  {
379  idx++;
380  }
381  if ( idx >= colorRampItemListCount )
382  {
383  idx = colorRampItemListCount - 1;
384  overflow = true;
385  }
386  }
387 
388  const QgsColorRampShader::ColorRampItem& currentColorRampItem = mColorRampItemList.at( idx );
389 
390  if ( colorRampType() == Interpolated )
391  { // Interpolate the color between two class breaks linearly.
392  if ( idx < 1 || overflow || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD <= value )
393  {
394  if ( mClip && ( overflow
395  || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD > value ) )
396  {
397  return false;
398  }
399  *returnRedValue = currentColorRampItem.color.red();
400  *returnGreenValue = currentColorRampItem.color.green();
401  *returnBlueValue = currentColorRampItem.color.blue();
402  *returnAlphaValue = currentColorRampItem.color.alpha();
403  return true;
404  }
405 
406  const QgsColorRampShader::ColorRampItem& previousColorRampItem = mColorRampItemList.at( idx - 1 );
407 
408  double currentRampRange = currentColorRampItem.value - previousColorRampItem.value;
409  double offsetInRange = value - previousColorRampItem.value;
410  double scale = offsetInRange / currentRampRange;
411 
412  *returnRedValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.red() ) + ( static_cast< double >( currentColorRampItem.color.red() - previousColorRampItem.color.red() ) * scale ) );
413  *returnGreenValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.green() ) + ( static_cast< double >( currentColorRampItem.color.green() - previousColorRampItem.color.green() ) * scale ) );
414  *returnBlueValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.blue() ) + ( static_cast< double >( currentColorRampItem.color.blue() - previousColorRampItem.color.blue() ) * scale ) );
415  *returnAlphaValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.alpha() ) + ( static_cast< double >( currentColorRampItem.color.alpha() - previousColorRampItem.color.alpha() ) * scale ) );
416  return true;
417  }
418  else if ( colorRampType() == Discrete )
419  { // Assign the color of the higher class for every pixel between two class breaks.
420  // NOTE: The implementation has always been different than the documentation,
421  // which said lower class before, see http://hub.qgis.org/issues/13995
422  if ( overflow )
423  {
424  return false;
425  }
426  *returnRedValue = currentColorRampItem.color.red();
427  *returnGreenValue = currentColorRampItem.color.green();
428  *returnBlueValue = currentColorRampItem.color.blue();
429  *returnAlphaValue = currentColorRampItem.color.alpha();
430  return true;
431  }
432  else // EXACT
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:36
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:37
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:166
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:109
QgsColorRampShader(double minimumValue=0.0, double maximumValue=255.0, QgsColorRamp *colorRamp=nullptr, Type type=Interpolated, ClassificationMode classificationMode=Continuous)
Creates a new color ramp shader.