1 /* **************************************************************************
2                 qgscontrastenhancement.cpp -  description
3                        -------------------
4 begin                : Mon Oct 22 2007
5 copyright            : (C) 2007 by Peter J. Ersts
6 email                : ersts@amnh.org
7 
8 This class contains code that was originally part of the larger QgsRasterLayer
9 class originally created circa 2004 by T.Sutton, Gary E.Sherman, Steve Halasz
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 #include "qgslogger.h"
22 
23 #include "qgscontrastenhancement.h"
24 #include "qgscontrastenhancementfunction.h"
25 #include "qgslinearminmaxenhancement.h"
26 #include "qgslinearminmaxenhancementwithclip.h"
27 #include "qgscliptominmaxenhancement.h"
28 #include "qgsrasterblock.h"
29 #include <QDomDocument>
30 #include <QDomElement>
31 
QgsContrastEnhancement(Qgis::DataType dataType)32 QgsContrastEnhancement::QgsContrastEnhancement( Qgis::DataType dataType )
33   : mMinimumValue( minimumValuePossible( dataType ) )
34   , mMaximumValue( maximumValuePossible( dataType ) )
35   , mRasterDataType( dataType )
36   , mRasterDataTypeRange( mMaximumValue - mMinimumValue )
37   , mLookupTableOffset( mMinimumValue * -1 )
38 {
39   mContrastEnhancementFunction.reset( new QgsContrastEnhancementFunction( mRasterDataType, mMinimumValue, mMaximumValue ) );
40 
41   //If the data type is larger than 16-bit do not generate a lookup table
42   if ( mRasterDataTypeRange <= 65535.0 )
43   {
44     mLookupTable = new int[static_cast <int>( mRasterDataTypeRange + 1 )];
45   }
46 }
47 
QgsContrastEnhancement(const QgsContrastEnhancement & ce)48 QgsContrastEnhancement::QgsContrastEnhancement( const QgsContrastEnhancement &ce )
49   : mEnhancementDirty( true )
50   , mMinimumValue( ce.mMinimumValue )
51   , mMaximumValue( ce.mMaximumValue )
52   , mRasterDataType( ce.mRasterDataType )
53   , mRasterDataTypeRange( ce.mRasterDataTypeRange )
54 {
55   mLookupTableOffset = minimumValuePossible( mRasterDataType ) * -1;
56 
57   // setContrastEnhancementAlgorithm sets also QgsContrastEnhancementFunction
58   setContrastEnhancementAlgorithm( ce.mContrastEnhancementAlgorithm, false );
59 
60   //If the data type is larger than 16-bit do not generate a lookup table
61   if ( mRasterDataTypeRange <= 65535.0 )
62   {
63     mLookupTable = new int[static_cast <int>( mRasterDataTypeRange + 1 )];
64   }
65 }
66 
~QgsContrastEnhancement()67 QgsContrastEnhancement::~QgsContrastEnhancement()
68 {
69   delete [] mLookupTable;
70 }
71 
enhanceContrast(double value)72 int QgsContrastEnhancement::enhanceContrast( double value )
73 {
74   if ( mEnhancementDirty )
75   {
76     generateLookupTable();
77   }
78 
79   if ( mLookupTable && NoEnhancement != mContrastEnhancementAlgorithm )
80   {
81     const double shiftedValue = value + mLookupTableOffset;
82     if ( shiftedValue >= 0 && shiftedValue < mRasterDataTypeRange + 1 )
83       return mLookupTable[static_cast <int>( shiftedValue )];
84     return 0;
85   }
86   else
87   {
88     // Even if the contrast enhancement algorithms is set to NoEnhancement
89     // The input values will still have to be scaled for all data types
90     // greater than 1 byte.
91     return mContrastEnhancementFunction->enhance( value );
92   }
93 }
94 
generateLookupTable()95 bool QgsContrastEnhancement::generateLookupTable()
96 {
97   mEnhancementDirty = false;
98 
99   if ( !mContrastEnhancementFunction )
100     return false;
101   if ( NoEnhancement == mContrastEnhancementAlgorithm )
102     return false;
103   if ( Qgis::DataType::Byte != mRasterDataType && Qgis::DataType::UInt16 != mRasterDataType && Qgis::DataType::Int16 != mRasterDataType )
104     return false;
105   if ( !mLookupTable )
106     return false;
107 
108   QgsDebugMsgLevel( QStringLiteral( "building lookup table" ), 4 );
109   QgsDebugMsgLevel( QStringLiteral( "***MinimumValue : %1" ).arg( mMinimumValue ), 4 );
110   QgsDebugMsgLevel( QStringLiteral( "***MaximumValue : %1" ).arg( mMaximumValue ), 4 );
111   QgsDebugMsgLevel( QStringLiteral( "***mLookupTableOffset : %1" ).arg( mLookupTableOffset ), 4 );
112   QgsDebugMsgLevel( QStringLiteral( "***mRasterDataTypeRange : %1" ).arg( mRasterDataTypeRange ), 4 );
113 
114   for ( int myIterator = 0; myIterator <= mRasterDataTypeRange; myIterator++ )
115   {
116     mLookupTable[myIterator] = mContrastEnhancementFunction->enhance( static_cast< double >( myIterator ) - mLookupTableOffset );
117   }
118 
119   return true;
120 }
121 
isValueInDisplayableRange(double value)122 bool QgsContrastEnhancement::isValueInDisplayableRange( double value )
123 {
124   if ( mContrastEnhancementFunction )
125   {
126     return mContrastEnhancementFunction->isValueInDisplayableRange( value );
127   }
128 
129   return false;
130 }
131 
setContrastEnhancementAlgorithm(ContrastEnhancementAlgorithm algorithm,bool generateTable)132 void QgsContrastEnhancement::setContrastEnhancementAlgorithm( ContrastEnhancementAlgorithm algorithm, bool generateTable )
133 {
134   switch ( algorithm )
135   {
136     case StretchToMinimumMaximum :
137       mContrastEnhancementFunction.reset( new QgsLinearMinMaxEnhancement( mRasterDataType, mMinimumValue, mMaximumValue ) );
138       break;
139     case StretchAndClipToMinimumMaximum :
140       mContrastEnhancementFunction.reset( new QgsLinearMinMaxEnhancementWithClip( mRasterDataType, mMinimumValue, mMaximumValue ) );
141       break;
142     case ClipToMinimumMaximum :
143       mContrastEnhancementFunction.reset( new QgsClipToMinMaxEnhancement( mRasterDataType, mMinimumValue, mMaximumValue ) );
144       break;
145     case UserDefinedEnhancement :
146       //Do nothing
147       break;
148     default:
149       mContrastEnhancementFunction.reset( new QgsContrastEnhancementFunction( mRasterDataType, mMinimumValue, mMaximumValue ) );
150       break;
151   }
152 
153   mEnhancementDirty = true;
154   mContrastEnhancementAlgorithm = algorithm;
155 
156   if ( generateTable )
157   {
158     generateLookupTable();
159   }
160 }
161 
setContrastEnhancementFunction(QgsContrastEnhancementFunction * function)162 void QgsContrastEnhancement::setContrastEnhancementFunction( QgsContrastEnhancementFunction *function )
163 {
164   QgsDebugMsgLevel( QStringLiteral( "called" ), 4 );
165 
166   if ( function )
167   {
168     mContrastEnhancementFunction.reset( function );
169     mContrastEnhancementAlgorithm = UserDefinedEnhancement;
170     generateLookupTable();
171   }
172 }
173 
setMaximumValue(double value,bool generateTable)174 void QgsContrastEnhancement::setMaximumValue( double value, bool generateTable )
175 {
176   QgsDebugMsgLevel( "called value: " + QString::number( value ) + " generate lookup table: " + QString::number( static_cast< int >( generateTable ) ), 4 );
177 
178   if ( value > maximumValuePossible( mRasterDataType ) )
179   {
180     mMaximumValue = maximumValuePossible( mRasterDataType );
181   }
182   else
183   {
184     mMaximumValue = value;
185   }
186 
187   if ( mContrastEnhancementFunction )
188   {
189     mContrastEnhancementFunction->setMaximumValue( value );
190   }
191 
192   mEnhancementDirty = true;
193 
194   if ( generateTable )
195   {
196     generateLookupTable();
197   }
198 }
199 
setMinimumValue(double value,bool generateTable)200 void QgsContrastEnhancement::setMinimumValue( double value, bool generateTable )
201 {
202   QgsDebugMsgLevel( "called value: " + QString::number( value ) + " generate lookup table: " + QString::number( static_cast< int >( generateTable ) ), 4 );
203 
204   if ( value < minimumValuePossible( mRasterDataType ) )
205   {
206     mMinimumValue = minimumValuePossible( mRasterDataType );
207   }
208   else
209   {
210     mMinimumValue = value;
211   }
212 
213   if ( mContrastEnhancementFunction )
214   {
215     mContrastEnhancementFunction->setMinimumValue( value );
216   }
217 
218   mEnhancementDirty = true;
219 
220   if ( generateTable )
221   {
222     generateLookupTable();
223   }
224 }
225 
writeXml(QDomDocument & doc,QDomElement & parentElem) const226 void QgsContrastEnhancement::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
227 {
228   //minimum value
229   QDomElement minElem = doc.createElement( QStringLiteral( "minValue" ) );
230   const QDomText minText = doc.createTextNode( QgsRasterBlock::printValue( mMinimumValue ) );
231   minElem.appendChild( minText );
232   parentElem.appendChild( minElem );
233 
234   //maximum value
235   QDomElement maxElem = doc.createElement( QStringLiteral( "maxValue" ) );
236   const QDomText maxText = doc.createTextNode( QgsRasterBlock::printValue( mMaximumValue ) );
237   maxElem.appendChild( maxText );
238   parentElem.appendChild( maxElem );
239 
240   //algorithm
241   QDomElement algorithmElem = doc.createElement( QStringLiteral( "algorithm" ) );
242   const QDomText algorithmText = doc.createTextNode( contrastEnhancementAlgorithmString( mContrastEnhancementAlgorithm ) );
243   algorithmElem.appendChild( algorithmText );
244   parentElem.appendChild( algorithmElem );
245 }
246 
readXml(const QDomElement & elem)247 void QgsContrastEnhancement::readXml( const QDomElement &elem )
248 {
249   const QDomElement minValueElem = elem.firstChildElement( QStringLiteral( "minValue" ) );
250   if ( !minValueElem.isNull() )
251   {
252     mMinimumValue = minValueElem.text().toDouble();
253   }
254   const QDomElement maxValueElem = elem.firstChildElement( QStringLiteral( "maxValue" ) );
255   if ( !maxValueElem.isNull() )
256   {
257     mMaximumValue = maxValueElem.text().toDouble();
258   }
259   const QDomElement algorithmElem = elem.firstChildElement( QStringLiteral( "algorithm" ) );
260   if ( !algorithmElem.isNull() )
261   {
262     const QString algorithmString = algorithmElem.text();
263     ContrastEnhancementAlgorithm algorithm = NoEnhancement;
264     // old version ( < 19 Apr 2013) was using enum directly -> for backward compatibility
265     if ( algorithmString == QLatin1String( "0" ) )
266     {
267       algorithm = NoEnhancement;
268     }
269     else if ( algorithmString == QLatin1String( "1" ) )
270     {
271       algorithm = StretchToMinimumMaximum;
272     }
273     else if ( algorithmString == QLatin1String( "2" ) )
274     {
275       algorithm = StretchAndClipToMinimumMaximum;
276     }
277     else if ( algorithmString == QLatin1String( "3" ) )
278     {
279       algorithm = ClipToMinimumMaximum;
280     }
281     else if ( algorithmString == QLatin1String( "4" ) )
282     {
283       algorithm = UserDefinedEnhancement;
284     }
285     else
286     {
287       algorithm = contrastEnhancementAlgorithmFromString( algorithmString );
288     }
289 
290     setContrastEnhancementAlgorithm( algorithm );
291   }
292 }
293 
toSld(QDomDocument & doc,QDomElement & element) const294 void QgsContrastEnhancement::toSld( QDomDocument &doc, QDomElement &element ) const
295 {
296   if ( doc.isNull() || element.isNull() )
297     return;
298 
299   QString algName;
300   switch ( contrastEnhancementAlgorithm() )
301   {
302     case StretchToMinimumMaximum:
303       algName = QStringLiteral( "StretchToMinimumMaximum" );
304       break;
305     /* TODO: check if ClipToZero => StretchAndClipToMinimumMaximum
306      * because value outside min/max ar considered as NoData instead of 0 */
307     case StretchAndClipToMinimumMaximum:
308       algName = QStringLiteral( "ClipToMinimumMaximum" );
309       break;
310     case ClipToMinimumMaximum:
311       algName = QStringLiteral( "ClipToMinimumMaximum" );
312       break;
313     case NoEnhancement:
314       return;
315     case UserDefinedEnhancement:
316       algName = contrastEnhancementAlgorithmString( contrastEnhancementAlgorithm() );
317       QgsDebugMsg( QObject::tr( "No SLD1.0 conversion yet for stretch algorithm %1" ).arg( algName ) );
318       return;
319   }
320 
321   // Only <Normalize> is supported
322   // minValue and maxValue are that values as set depending on "Min /Max value settings"
323   // parameters
324   QDomElement normalizeElem = doc.createElement( QStringLiteral( "sld:Normalize" ) );
325   element.appendChild( normalizeElem );
326 
327   QDomElement vendorOptionAlgorithmElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
328   vendorOptionAlgorithmElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "algorithm" ) );
329   vendorOptionAlgorithmElem.appendChild( doc.createTextNode( algName ) );
330   normalizeElem.appendChild( vendorOptionAlgorithmElem );
331 
332   QDomElement vendorOptionMinValueElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
333   vendorOptionMinValueElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "minValue" ) );
334   vendorOptionMinValueElem.appendChild( doc.createTextNode( QString::number( minimumValue() ) ) );
335   normalizeElem.appendChild( vendorOptionMinValueElem );
336 
337   QDomElement vendorOptionMaxValueElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
338   vendorOptionMaxValueElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "maxValue" ) );
339   vendorOptionMaxValueElem.appendChild( doc.createTextNode( QString::number( maximumValue() ) ) );
340   normalizeElem.appendChild( vendorOptionMaxValueElem );
341 }
342 
contrastEnhancementAlgorithmString(ContrastEnhancementAlgorithm algorithm)343 QString QgsContrastEnhancement::contrastEnhancementAlgorithmString( ContrastEnhancementAlgorithm algorithm )
344 {
345   switch ( algorithm )
346   {
347     case NoEnhancement:
348       return QStringLiteral( "NoEnhancement" );
349     case StretchToMinimumMaximum:
350       return QStringLiteral( "StretchToMinimumMaximum" );
351     case StretchAndClipToMinimumMaximum:
352       return QStringLiteral( "StretchAndClipToMinimumMaximum" );
353     case ClipToMinimumMaximum:
354       return QStringLiteral( "ClipToMinimumMaximum" );
355     case UserDefinedEnhancement:
356       return QStringLiteral( "UserDefinedEnhancement" );
357   }
358   return QStringLiteral( "NoEnhancement" );
359 }
360 
contrastEnhancementAlgorithmFromString(const QString & contrastEnhancementString)361 QgsContrastEnhancement::ContrastEnhancementAlgorithm QgsContrastEnhancement::contrastEnhancementAlgorithmFromString( const QString &contrastEnhancementString )
362 {
363   if ( contrastEnhancementString == QLatin1String( "StretchToMinimumMaximum" ) )
364   {
365     return StretchToMinimumMaximum;
366   }
367   else if ( contrastEnhancementString == QLatin1String( "StretchAndClipToMinimumMaximum" ) )
368   {
369     return StretchAndClipToMinimumMaximum;
370   }
371   else if ( contrastEnhancementString == QLatin1String( "ClipToMinimumMaximum" ) )
372   {
373     return ClipToMinimumMaximum;
374   }
375   else if ( contrastEnhancementString == QLatin1String( "UserDefinedEnhancement" ) )
376   {
377     return UserDefinedEnhancement;
378   }
379   else
380   {
381     return NoEnhancement;
382   }
383 }
384