QGIS API Documentation  2.99.0-Master (e077efd)
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 
30 #include <cmath>
31 
32 QgsColorRampShader::QgsColorRampShader( double theMinimumValue, double theMaximumValue )
33  : QgsRasterShaderFunction( theMinimumValue, theMaximumValue )
34  , mColorRampType( INTERPOLATED )
35  , mLUTOffset( 0.0 )
36  , mLUTFactor( 1.0 )
37  , mLUTInitialized( false )
38  , mClip( false )
39 {
40  QgsDebugMsgLevel( "called.", 4 );
41 }
42 
44  : QgsRasterShaderFunction( other )
45  , mLUT( other.mLUT )
46  , mLUTOffset( other.mLUTOffset )
47  , mLUTFactor( other.mLUTFactor )
48  , mLUTInitialized( other.mLUTInitialized )
49  , mClip( other.mClip )
50 {
51  mSourceColorRamp.reset( other.sourceColorRamp()->clone() );
52 }
53 
55 {
56  mSourceColorRamp.reset( other.sourceColorRamp()->clone() );
57  mLUT = other.mLUT;
58  mLUTOffset = other.mLUTOffset;
59  mLUTFactor = other.mLUTFactor;
60  mLUTInitialized = other.mLUTInitialized;
61  mClip = other.mClip;
62  return *this;
63 }
64 
66 {
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>& theList )
84 {
85  mColorRampItemList = theList.toVector();
86  // Reset the look up table when the color ramp is changed
87  mLUTInitialized = false;
88  mLUT.clear();
89 }
90 
92 {
93  mColorRampType = theColorRampType;
94 }
95 
96 void QgsColorRampShader::setColorRampType( const QString& theType )
97 {
98  if ( theType == QLatin1String( "INTERPOLATED" ) )
99  {
100  mColorRampType = INTERPOLATED;
101  }
102  else if ( theType == QLatin1String( "DISCRETE" ) )
103  {
104  mColorRampType = DISCRETE;
105  }
106  else
107  {
108  mColorRampType = EXACT;
109  }
110 }
111 
113 {
114  return mSourceColorRamp.data();
115 }
116 
118 {
119  mSourceColorRamp.reset( colorramp );
120 }
121 
122 
123 bool QgsColorRampShader::shade( double theValue, int* theReturnRedValue, int* theReturnGreenValue, int* theReturnBlueValue, int *theReturnAlphaValue )
124 {
125  if ( mColorRampItemList.isEmpty() )
126  {
127  return false;
128  }
129  if ( qIsNaN( theValue ) || qIsInf( theValue ) )
130  return false;
131 
132  int colorRampItemListCount = mColorRampItemList.count();
133  int idx;
134  if ( !mLUTInitialized )
135  {
136  // calculate LUT for faster index recovery
137  mLUTFactor = 1.0;
138  double minimumValue = mColorRampItemList.first().value;
139  mLUTOffset = minimumValue + DOUBLE_DIFF_THRESHOLD;
140  // Only make lut if at least 3 items, with 2 items the low and high cases handle both
141  if ( colorRampItemListCount >= 3 )
142  {
143  double rangeValue = mColorRampItemList.at( colorRampItemListCount - 2 ).value - minimumValue;
144  if ( rangeValue > 0 )
145  {
146  int lutSize = 256; // TODO: test if speed can be increased with a different LUT size
147  mLUTFactor = ( lutSize - 0.0000001 ) / rangeValue; // decrease slightly to make sure last LUT category is correct
148  idx = 0;
149  double val;
150  mLUT.reserve( lutSize );
151  for ( int i = 0; i < lutSize; i++ )
152  {
153  val = ( i / mLUTFactor ) + mLUTOffset;
154  while ( idx < colorRampItemListCount
155  && mColorRampItemList.at( idx ).value - DOUBLE_DIFF_THRESHOLD < val )
156  {
157  idx++;
158  }
159  mLUT.push_back( idx );
160  }
161  }
162  }
163  mLUTInitialized = true;
164  }
165 
166  // overflow indicates that theValue > maximum value + DOUBLE_DIFF_THRESHOLD
167  // that way idx can point to the last valid item
168  bool overflow = false;
169 
170  // find index of the first ColorRampItem that is equal or higher to theValue
171  int lutIndex = ( theValue - mLUTOffset ) * mLUTFactor;
172  if ( theValue < mLUTOffset )
173  {
174  idx = 0;
175  }
176  else if ( lutIndex >= mLUT.count() )
177  {
178  idx = colorRampItemListCount - 1;
179  if ( mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < theValue )
180  {
181  overflow = true;
182  }
183  }
184  else
185  {
186  // get initial value from LUT
187  idx = mLUT.at( lutIndex );
188 
189  // check if it's correct and if not increase untill correct
190  // the LUT is made in such a way the index is always correct or too low, never too high
191  while ( idx < colorRampItemListCount && mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < theValue )
192  {
193  idx++;
194  }
195  if ( idx >= colorRampItemListCount )
196  {
197  idx = colorRampItemListCount - 1;
198  overflow = true;
199  }
200  }
201 
202  const QgsColorRampShader::ColorRampItem& currentColorRampItem = mColorRampItemList.at( idx );
203 
204  if ( QgsColorRampShader::INTERPOLATED == mColorRampType )
205  { // Interpolate the color between two class breaks linearly.
206  if ( idx < 1 || overflow || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD <= theValue )
207  {
208  if ( mClip && ( overflow
209  || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD > theValue ) )
210  {
211  return false;
212  }
213  *theReturnRedValue = currentColorRampItem.color.red();
214  *theReturnGreenValue = currentColorRampItem.color.green();
215  *theReturnBlueValue = currentColorRampItem.color.blue();
216  *theReturnAlphaValue = currentColorRampItem.color.alpha();
217  return true;
218  }
219 
220  const QgsColorRampShader::ColorRampItem& previousColorRampItem = mColorRampItemList.at( idx - 1 );
221 
222  double currentRampRange = currentColorRampItem.value - previousColorRampItem.value;
223  double offsetInRange = theValue - previousColorRampItem.value;
224  double scale = offsetInRange / currentRampRange;
225 
226  *theReturnRedValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.red() ) + ( static_cast< double >( currentColorRampItem.color.red() - previousColorRampItem.color.red() ) * scale ) );
227  *theReturnGreenValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.green() ) + ( static_cast< double >( currentColorRampItem.color.green() - previousColorRampItem.color.green() ) * scale ) );
228  *theReturnBlueValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.blue() ) + ( static_cast< double >( currentColorRampItem.color.blue() - previousColorRampItem.color.blue() ) * scale ) );
229  *theReturnAlphaValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.alpha() ) + ( static_cast< double >( currentColorRampItem.color.alpha() - previousColorRampItem.color.alpha() ) * scale ) );
230  return true;
231  }
232  else if ( QgsColorRampShader::DISCRETE == mColorRampType )
233  { // Assign the color of the higher class for every pixel between two class breaks.
234  // NOTE: The implementation has always been different than the documentation,
235  // which said lower class before, see http://hub.qgis.org/issues/13995
236  if ( overflow )
237  {
238  return false;
239  }
240  *theReturnRedValue = currentColorRampItem.color.red();
241  *theReturnGreenValue = currentColorRampItem.color.green();
242  *theReturnBlueValue = currentColorRampItem.color.blue();
243  *theReturnAlphaValue = currentColorRampItem.color.alpha();
244  return true;
245  }
246  else // EXACT
247  { // Assign the color of the exact matching value in the color ramp item list
248  if ( !overflow && currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD <= theValue )
249  {
250  *theReturnRedValue = currentColorRampItem.color.red();
251  *theReturnGreenValue = currentColorRampItem.color.green();
252  *theReturnBlueValue = currentColorRampItem.color.blue();
253  *theReturnAlphaValue = currentColorRampItem.color.alpha();
254  return true;
255  }
256  else
257  {
258  return false;
259  }
260  }
261 }
262 
263 bool QgsColorRampShader::shade( double theRedValue, double theGreenValue,
264  double theBlueValue, double theAlphaValue,
265  int* theReturnRedValue, int* theReturnGreenValue,
266  int* theReturnBlueValue, int* theReturnAlphaValue )
267 {
268  Q_UNUSED( theRedValue );
269  Q_UNUSED( theGreenValue );
270  Q_UNUSED( theBlueValue );
271  Q_UNUSED( theAlphaValue );
272 
273  *theReturnRedValue = 0;
274  *theReturnGreenValue = 0;
275  *theReturnBlueValue = 0;
276  *theReturnAlphaValue = 0;
277 
278  return false;
279 }
280 
281 void QgsColorRampShader::legendSymbologyItems( QList< QPair< QString, QColor > >& symbolItems ) const
282 {
283  QVector<QgsColorRampShader::ColorRampItem>::const_iterator colorRampIt = mColorRampItemList.constBegin();
284  for ( ; colorRampIt != mColorRampItemList.constEnd(); ++colorRampIt )
285  {
286  symbolItems.push_back( qMakePair( colorRampIt->label, colorRampIt->color ) );
287  }
288 }
Assigns the color of the exact matching value in the color ramp item list.
QScopedPointer< QgsColorRamp > mSourceColorRamp
Source color ramp.
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.
QgsColorRampShader(double theMinimumValue=0.0, double theMaximumValue=255.0)
Abstract base class for color ramps.
Definition: qgscolorramp.h:29
QgsColorRampShader & operator=(const QgsColorRampShader &other)
Assignment operator.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &theList)
Set custom colormap.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
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...
void legendSymbologyItems(QList< QPair< QString, QColor > > &symbolItems) const override
Get symbology items if provided by renderer.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
void setColorRampType(QgsColorRampShader::ColorRamp_TYPE theColorRampType)
Set the color ramp type.
QString colorRampTypeAsQString()
Get the color ramp type as a string.
virtual ~QgsColorRampShader()
Destructor.
Interpolates the color between two class breaks linearly.
Assigns the color of the higher class for every pixel between two class breaks.
#define DOUBLE_DIFF_THRESHOLD
QgsColorRamp * sourceColorRamp() const
Get the source color ramp.
ColorRamp_TYPE
Supported methods for color interpolation.