QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgssinglebandgrayrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssinglebandgrayrenderer.cpp
3  -----------------------------
4  begin : December 2011
5  copyright : (C) 2011 by Marco Hugentobler
6  email : marco at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
19 #include "qgscontrastenhancement.h"
20 #include "qgsrastertransparency.h"
21 #include <QDomDocument>
22 #include <QDomElement>
23 #include <QImage>
24 #include <QColor>
25 #include <memory>
26 
28  : QgsRasterRenderer( input, QStringLiteral( "singlebandgray" ) )
29  , mGrayBand( grayBand )
30  , mGradient( BlackToWhite )
31  , mContrastEnhancement( nullptr )
32 {
33 }
34 
36 {
37  QgsSingleBandGrayRenderer *renderer = new QgsSingleBandGrayRenderer( nullptr, mGrayBand );
38  renderer->copyCommonProperties( this );
39 
40  renderer->setGradient( mGradient );
41  if ( mContrastEnhancement )
42  {
43  renderer->setContrastEnhancement( new QgsContrastEnhancement( *mContrastEnhancement ) );
44  }
45  return renderer;
46 }
47 
49 {
50  if ( elem.isNull() )
51  {
52  return nullptr;
53  }
54 
55  int grayBand = elem.attribute( QStringLiteral( "grayBand" ), QStringLiteral( "-1" ) ).toInt();
56  QgsSingleBandGrayRenderer *r = new QgsSingleBandGrayRenderer( input, grayBand );
57  r->readXml( elem );
58 
59  if ( elem.attribute( QStringLiteral( "gradient" ) ) == QLatin1String( "WhiteToBlack" ) )
60  {
61  r->setGradient( WhiteToBlack ); // BlackToWhite is default
62  }
63 
64  QDomElement contrastEnhancementElem = elem.firstChildElement( QStringLiteral( "contrastEnhancement" ) );
65  if ( !contrastEnhancementElem.isNull() )
66  {
68  input->dataType( grayBand ) ) );
69  ce->readXml( contrastEnhancementElem );
70  r->setContrastEnhancement( ce );
71  }
72  return r;
73 }
74 
76 {
77  mContrastEnhancement.reset( ce );
78 }
79 
80 QgsRasterBlock *QgsSingleBandGrayRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
81 {
82  Q_UNUSED( bandNo );
83  QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2" ).arg( width ).arg( height ), 4 );
84 
85  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
86  if ( !mInput )
87  {
88  return outputBlock.release();
89  }
90 
91  std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mGrayBand, extent, width, height, feedback ) );
92  if ( !inputBlock || inputBlock->isEmpty() )
93  {
94  QgsDebugMsg( QStringLiteral( "No raster data!" ) );
95  return outputBlock.release();
96  }
97 
98  std::shared_ptr< QgsRasterBlock > alphaBlock;
99 
100  if ( mAlphaBand > 0 && mGrayBand != mAlphaBand )
101  {
102  alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
103  if ( !alphaBlock || alphaBlock->isEmpty() )
104  {
105  // TODO: better to render without alpha
106  return outputBlock.release();
107  }
108  }
109  else if ( mAlphaBand > 0 )
110  {
111  alphaBlock = inputBlock;
112  }
113 
114  if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
115  {
116  return outputBlock.release();
117  }
118 
119  QRgb myDefaultColor = NODATA_COLOR;
120  for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
121  {
122  if ( inputBlock->isNoData( i ) )
123  {
124  outputBlock->setColor( i, myDefaultColor );
125  continue;
126  }
127  double grayVal = inputBlock->value( i );
128 
129  double currentAlpha = mOpacity;
130  if ( mRasterTransparency )
131  {
132  currentAlpha = mRasterTransparency->alphaValue( grayVal, mOpacity * 255 ) / 255.0;
133  }
134  if ( mAlphaBand > 0 )
135  {
136  currentAlpha *= alphaBlock->value( i ) / 255.0;
137  }
138 
139  if ( mContrastEnhancement )
140  {
141  if ( !mContrastEnhancement->isValueInDisplayableRange( grayVal ) )
142  {
143  outputBlock->setColor( i, myDefaultColor );
144  continue;
145  }
146  grayVal = mContrastEnhancement->enhanceContrast( grayVal );
147  }
148 
149  if ( mGradient == WhiteToBlack )
150  {
151  grayVal = 255 - grayVal;
152  }
153 
154  if ( qgsDoubleNear( currentAlpha, 1.0 ) )
155  {
156  outputBlock->setColor( i, qRgba( grayVal, grayVal, grayVal, 255 ) );
157  }
158  else
159  {
160  outputBlock->setColor( i, qRgba( currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * 255 ) );
161  }
162  }
163 
164  return outputBlock.release();
165 }
166 
167 void QgsSingleBandGrayRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
168 {
169  if ( parentElem.isNull() )
170  {
171  return;
172  }
173 
174  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
175  _writeXml( doc, rasterRendererElem );
176 
177  rasterRendererElem.setAttribute( QStringLiteral( "grayBand" ), mGrayBand );
178 
179  QString gradient;
180  if ( mGradient == BlackToWhite )
181  {
182  gradient = QStringLiteral( "BlackToWhite" );
183  }
184  else
185  {
186  gradient = QStringLiteral( "WhiteToBlack" );
187  }
188  rasterRendererElem.setAttribute( QStringLiteral( "gradient" ), gradient );
189 
190  if ( mContrastEnhancement )
191  {
192  QDomElement contrastElem = doc.createElement( QStringLiteral( "contrastEnhancement" ) );
193  mContrastEnhancement->writeXml( doc, contrastElem );
194  rasterRendererElem.appendChild( contrastElem );
195  }
196  parentElem.appendChild( rasterRendererElem );
197 }
198 
199 void QgsSingleBandGrayRenderer::legendSymbologyItems( QList< QPair< QString, QColor > > &symbolItems ) const
200 {
201  if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
202  {
203  QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
204  QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
205  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->minimumValue() ), minColor ) );
206  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->maximumValue() ), maxColor ) );
207  }
208 }
209 
211 {
212  QList<int> bandList;
213  if ( mGrayBand != -1 )
214  {
215  bandList << mGrayBand;
216  }
217  return bandList;
218 }
219 
220 void QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
221 {
222  QgsStringMap newProps = props;
223 
224  // create base structure
225  QgsRasterRenderer::toSld( doc, element, props );
226 
227  // look for RasterSymbolizer tag
228  QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
229  if ( elements.size() == 0 )
230  return;
231 
232  // there SHOULD be only one
233  QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
234 
235  // add Channel Selection tags
236  // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
237  // after opacity or geometry or as first element after sld:RasterSymbolizer
238  QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
239  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
240  if ( elements.size() != 0 )
241  {
242  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
243  }
244  else
245  {
246  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
247  if ( elements.size() != 0 )
248  {
249  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
250  }
251  else
252  {
253  rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
254  }
255  }
256 
257  // for gray band
258  QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
259  channelSelectionElem.appendChild( channelElem );
260 
261  // set band
262  QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
263  sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( grayBand() ) ) );
264  channelElem.appendChild( sourceChannelNameElem );
265 
266  // set ContrastEnhancement
267  if ( contrastEnhancement() )
268  {
269  QDomElement contrastEnhancementElem = doc.createElement( QStringLiteral( "sld:ContrastEnhancement" ) );
270  contrastEnhancement()->toSld( doc, contrastEnhancementElem );
271 
272  // do changes to minValue/maxValues depending on stretching algorithm. This is necessary because
273  // geoserver do a first stretch on min/max, then apply colo map rules. In some combination is necessary
274  // to use real min/max values and in othere the actual edited min/max values
275  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
276  {
279  {
280  // with this renderer export have to be check against real min/max values of the raster
282 
283  // if minimum range differ from the real minimum => set is in exported SLD vendor option
284  if ( !qgsDoubleNear( contrastEnhancement()->minimumValue(), myRasterBandStats.minimumValue ) )
285  {
286  // look for VendorOption tag to look for that with minValue attribute
287  QDomNodeList elements = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
288  for ( int i = 0; i < elements.size(); ++i )
289  {
290  QDomElement vendorOption = elements.at( i ).toElement();
291  if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QStringLiteral( "minValue" ) )
292  continue;
293 
294  // remove old value and add the new one
295  vendorOption.removeChild( vendorOption.firstChild() );
296  vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
297  }
298  }
299  break;
300  }
302  break;
304  break;
306  break;
307  }
308 
309  channelElem.appendChild( contrastEnhancementElem );
310  }
311 
312  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
313  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
314  QList< QPair< QString, QColor > > classes;
315  legendSymbologyItems( classes );
316 
317  // add ColorMap tag
318  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
319  rasterSymbolizerElem.appendChild( colorMapElem );
320 
321  // TODO: add clip intervals basing on real min/max without trigger
322  // min/max calculation again that can takes a lot for remote or big images
323  //
324  // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
325  // each ContrastEnhancementAlgorithm need a specific management.
326  // set type of ColorMap ramp [ramp, intervals, values]
327  // basing on interpolation algorithm of the raster shader
328  QList< QPair< QString, QColor > > colorMapping( classes );
329  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
330  {
333  {
334  QString lowValue = classes[0].first;
335  QColor lowColor = classes[0].second;
336  lowColor.setAlpha( 0 );
337  QString highValue = classes[1].first;
338  QColor highColor = classes[1].second;
339  highColor.setAlpha( 0 );
340 
341  colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
342  colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
343  break;
344  }
346  {
347  colorMapping[0].first = QStringLiteral( "0" );
348  colorMapping[1].first = QStringLiteral( "255" );
349  break;
350  }
352  break;
354  break;
355  }
356 
357  // create tags
358  QList< QPair< QString, QColor > >::ConstIterator it;
359  for ( it = colorMapping.begin(); it != colorMapping.constEnd() ; ++it )
360  {
361  // set low level color mapping
362  QDomElement lowColorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
363  colorMapElem.appendChild( lowColorMapEntryElem );
364  lowColorMapEntryElem.setAttribute( QStringLiteral( "color" ), it->second.name() );
365  lowColorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), it->first );
366  if ( it->second.alphaF() == 0.0 )
367  {
368  lowColorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( it->second.alpha() ) );
369  }
370  }
371 }
int alphaValue(double value, int globalTransparency=255) const
Returns the transparency value for a single value pixel.
A rectangle specified with double values.
Definition: qgsrectangle.h:40
static QgsRasterRenderer * create(const QDomElement &elem, QgsRasterInterface *input)
QgsSingleBandGrayRenderer(QgsRasterInterface *input, int grayBand)
QList< int > usesBands() const override
Returns a list of band numbers used by the renderer.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
const QgsContrastEnhancement * contrastEnhancement() const
DataType
Raster data types.
Definition: qgis.h:92
virtual QgsRasterInterface * input() const
Current input.
virtual Qgis::DataType dataType(int bandNo) const =0
Returns data type for the band specified by number.
QgsSingleBandGrayRenderer * clone() const override
Clone itself, create deep copy.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:577
QgsRasterTransparency * mRasterTransparency
Raster transparency per color or value. Overwrites global alpha value.
void legendSymbologyItems(QList< QPair< QString, QColor > > &symbolItems) const override
Gets symbology items if provided by renderer.
static const QRgb NODATA_COLOR
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:107
virtual void toSld(QDomDocument &doc, QDomElement &element, const QgsStringMap &props=QgsStringMap()) const
Used from subclasses to create SLD Rule elements following SLD v1.0 specs.
The RasterBandStats struct is a container for statistics about a single raster band.
Raster data container.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
virtual QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr)=0
Read block of data using given extent and size.
void toSld(QDomDocument &doc, QDomElement &element) const
Write ContrastEnhancement tags following SLD v1.0 specs SLD1.0 is limited to the parameters listed in...
void copyCommonProperties(const QgsRasterRenderer *other, bool copyMinMaxOrigin=true)
Copies common properties like opacity / transparency data from other renderer.
void readXml(const QDomElement &elem)
Raster renderer pipe for single band gray.
void setGradient(Gradient gradient)
int mAlphaBand
Read alpha value from band.
void readXml(const QDomElement &rendererElem) override
Sets base class members from xml. Usually called from create() methods of subclasses.
Base class for processing filters like renderers, reprojector, resampler etc.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition: qgis.h:586
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
void setContrastEnhancement(QgsContrastEnhancement *ce)
Takes ownership.
virtual QgsRectangle extent() const
Gets the extent of the interface.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
void toSld(QDomDocument &doc, QDomElement &element, const QgsStringMap &props=QgsStringMap()) const override
Used from subclasses to create SLD Rule elements following SLD v1.0 specs.
double minimumValue
The minimum cell value in the raster band.
double mOpacity
Global alpha value (0-1)
Manipulates raster pixel values so that they enhanceContrast or clip into a specified numerical range...
void writeXml(QDomDocument &doc, QDomElement &parentElem) const override
Write base class members to xml.
QgsRasterInterface * mInput
void _writeXml(QDomDocument &doc, QDomElement &rasterRendererElem) const
Write upper class info into rasterrenderer element (called by writeXml method of subclasses) ...
Feedback object tailored for raster block reading.
Raster renderer pipe that applies colors to a raster.