QGIS API Documentation  2.15.0-Master (af20121)
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 "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  int colorRampItemListCount = mColorRampItemList.count();
92  int idx;
93  if ( !mLUTInitialized )
94  {
95  // calculate LUT for faster index recovery
96  mLUTFactor = 1.0;
97  double minimumValue = mColorRampItemList.first().value;
98  mLUTOffset = minimumValue + DOUBLE_DIFF_THRESHOLD;
99  // Only make lut if at least 3 items, with 2 items the low and high cases handle both
100  if ( colorRampItemListCount >= 3 )
101  {
102  double rangeValue = mColorRampItemList.at( colorRampItemListCount - 2 ).value - minimumValue;
103  if ( rangeValue > 0 )
104  {
105  int lutSize = 256; // TODO: test if speed can be increased with a different LUT size
106  mLUTFactor = ( lutSize - 0.0000001 ) / rangeValue; // decrease slightly to make sure last LUT category is correct
107  idx = 0;
108  double val;
109  mLUT.reserve( lutSize );
110  for ( int i = 0; i < lutSize; i++ )
111  {
112  val = ( i / mLUTFactor ) + mLUTOffset;
113  while ( idx < colorRampItemListCount
114  && mColorRampItemList.at( idx ).value - DOUBLE_DIFF_THRESHOLD < val )
115  {
116  idx++;
117  }
118  mLUT.push_back( idx );
119  }
120  }
121  }
122  mLUTInitialized = true;
123  }
124 
125  // overflow indicates that theValue > maximum value + DOUBLE_DIFF_THRESHOLD
126  // that way idx can point to the last valid item
127  bool overflow = false;
128 
129  // find index of the first ColorRampItem that is equal or higher to theValue
130  int lutIndex = ( theValue - mLUTOffset ) * mLUTFactor;
131  if ( theValue < mLUTOffset )
132  {
133  idx = 0;
134  }
135  else if ( lutIndex >= mLUT.count() )
136  {
137  idx = colorRampItemListCount - 1;
138  if ( mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < theValue )
139  {
140  overflow = true;
141  }
142  }
143  else
144  {
145  // get initial value from LUT
146  idx = mLUT.at( lutIndex );
147 
148  // check if it's correct and if not increase untill correct
149  // the LUT is made in such a way the index is always correct or too low, never too high
150  while ( idx < colorRampItemListCount && mColorRampItemList.at( idx ).value + DOUBLE_DIFF_THRESHOLD < theValue )
151  {
152  idx++;
153  }
154  if ( idx >= colorRampItemListCount )
155  {
156  idx = colorRampItemListCount - 1;
157  overflow = true;
158  }
159  }
160 
161  const QgsColorRampShader::ColorRampItem& currentColorRampItem = mColorRampItemList.at( idx );
162 
163  if ( QgsColorRampShader::INTERPOLATED == mColorRampType )
164  { // Interpolate the color between two class breaks linearly.
165  if ( idx < 1 || overflow || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD <= theValue )
166  {
167  if ( mClip && ( overflow
168  || currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD > theValue ) )
169  {
170  return false;
171  }
172  *theReturnRedValue = currentColorRampItem.color.red();
173  *theReturnGreenValue = currentColorRampItem.color.green();
174  *theReturnBlueValue = currentColorRampItem.color.blue();
175  *theReturnAlphaValue = currentColorRampItem.color.alpha();
176  return true;
177  }
178 
179  const QgsColorRampShader::ColorRampItem& previousColorRampItem = mColorRampItemList.at( idx - 1 );
180 
181  double currentRampRange = currentColorRampItem.value - previousColorRampItem.value;
182  double offsetInRange = theValue - previousColorRampItem.value;
183  double scale = offsetInRange / currentRampRange;
184 
185  *theReturnRedValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.red() ) + ( static_cast< double >( currentColorRampItem.color.red() - previousColorRampItem.color.red() ) * scale ) );
186  *theReturnGreenValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.green() ) + ( static_cast< double >( currentColorRampItem.color.green() - previousColorRampItem.color.green() ) * scale ) );
187  *theReturnBlueValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.blue() ) + ( static_cast< double >( currentColorRampItem.color.blue() - previousColorRampItem.color.blue() ) * scale ) );
188  *theReturnAlphaValue = static_cast< int >( static_cast< double >( previousColorRampItem.color.alpha() ) + ( static_cast< double >( currentColorRampItem.color.alpha() - previousColorRampItem.color.alpha() ) * scale ) );
189  return true;
190  }
191  else if ( QgsColorRampShader::DISCRETE == mColorRampType )
192  { // Assign the color of the higher class for every pixel between two class breaks.
193  // NOTE: The implementation has always been different than the documentation,
194  // which said lower class before, see http://hub.qgis.org/issues/13995
195  if ( overflow )
196  {
197  return false;
198  }
199  *theReturnRedValue = currentColorRampItem.color.red();
200  *theReturnGreenValue = currentColorRampItem.color.green();
201  *theReturnBlueValue = currentColorRampItem.color.blue();
202  *theReturnAlphaValue = currentColorRampItem.color.alpha();
203  return true;
204  }
205  else // EXACT
206  { // Assign the color of the exact matching value in the color ramp item list
207  if ( !overflow && currentColorRampItem.value - DOUBLE_DIFF_THRESHOLD <= theValue )
208  {
209  *theReturnRedValue = currentColorRampItem.color.red();
210  *theReturnGreenValue = currentColorRampItem.color.green();
211  *theReturnBlueValue = currentColorRampItem.color.blue();
212  *theReturnAlphaValue = currentColorRampItem.color.alpha();
213  return true;
214  }
215  else
216  {
217  return false;
218  }
219  }
220 }
221 
222 bool QgsColorRampShader::shade( double theRedValue, double theGreenValue,
223  double theBlueValue, double theAlphaValue,
224  int* theReturnRedValue, int* theReturnGreenValue,
225  int* theReturnBlueValue, int* theReturnAlphaValue )
226 {
227  Q_UNUSED( theRedValue );
228  Q_UNUSED( theGreenValue );
229  Q_UNUSED( theBlueValue );
230  Q_UNUSED( theAlphaValue );
231 
232  *theReturnRedValue = 0;
233  *theReturnGreenValue = 0;
234  *theReturnBlueValue = 0;
235  *theReturnAlphaValue = 0;
236 
237  return false;
238 }
239 
241 {
242  QVector<QgsColorRampShader::ColorRampItem>::const_iterator colorRampIt = mColorRampItemList.constBegin();
243  for ( ; colorRampIt != mColorRampItemList.constEnd(); ++colorRampIt )
244  {
245  symbolItems.push_back( qMakePair( colorRampIt->label, colorRampIt->color ) );
246  }
247 }
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.