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