QGIS API Documentation  3.13.0-Master (b73bd58cfb)
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  const QRgb myDefaultColor = renderColorForNodataPixel();
120  bool isNoData = false;
121  for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
122  {
123  double grayVal = inputBlock->valueAndNoData( i, isNoData );
124 
125  if ( isNoData )
126  {
127  outputBlock->setColor( i, myDefaultColor );
128  continue;
129  }
130 
131  double currentAlpha = mOpacity;
132  if ( mRasterTransparency )
133  {
134  currentAlpha = mRasterTransparency->alphaValue( grayVal, mOpacity * 255 ) / 255.0;
135  }
136  if ( mAlphaBand > 0 )
137  {
138  currentAlpha *= alphaBlock->value( i ) / 255.0;
139  }
140 
141  if ( mContrastEnhancement )
142  {
143  if ( !mContrastEnhancement->isValueInDisplayableRange( grayVal ) )
144  {
145  outputBlock->setColor( i, myDefaultColor );
146  continue;
147  }
148  grayVal = mContrastEnhancement->enhanceContrast( grayVal );
149  }
150 
151  if ( mGradient == WhiteToBlack )
152  {
153  grayVal = 255 - grayVal;
154  }
155 
156  if ( qgsDoubleNear( currentAlpha, 1.0 ) )
157  {
158  outputBlock->setColor( i, qRgba( grayVal, grayVal, grayVal, 255 ) );
159  }
160  else
161  {
162  outputBlock->setColor( i, qRgba( currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * 255 ) );
163  }
164  }
165 
166  return outputBlock.release();
167 }
168 
169 void QgsSingleBandGrayRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
170 {
171  if ( parentElem.isNull() )
172  {
173  return;
174  }
175 
176  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
177  _writeXml( doc, rasterRendererElem );
178 
179  rasterRendererElem.setAttribute( QStringLiteral( "grayBand" ), mGrayBand );
180 
181  QString gradient;
182  if ( mGradient == BlackToWhite )
183  {
184  gradient = QStringLiteral( "BlackToWhite" );
185  }
186  else
187  {
188  gradient = QStringLiteral( "WhiteToBlack" );
189  }
190  rasterRendererElem.setAttribute( QStringLiteral( "gradient" ), gradient );
191 
192  if ( mContrastEnhancement )
193  {
194  QDomElement contrastElem = doc.createElement( QStringLiteral( "contrastEnhancement" ) );
195  mContrastEnhancement->writeXml( doc, contrastElem );
196  rasterRendererElem.appendChild( contrastElem );
197  }
198  parentElem.appendChild( rasterRendererElem );
199 }
200 
201 void QgsSingleBandGrayRenderer::legendSymbologyItems( QList< QPair< QString, QColor > > &symbolItems ) const
202 {
203  if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
204  {
205  QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
206  QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
207  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->minimumValue() ), minColor ) );
208  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->maximumValue() ), maxColor ) );
209  }
210 }
211 
213 {
214  QList<int> bandList;
215  if ( mGrayBand != -1 )
216  {
217  bandList << mGrayBand;
218  }
219  return bandList;
220 }
221 
222 void QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
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 does a first stretch on min/max, then applies color map rules.
274  // In some combination it is necessary to use real min/max values and in
275  // others the actual edited min/max values
276  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
277  {
280  {
281  // with this renderer export have to be check against real min/max values of the raster
283 
284  // if minimum range differ from the real minimum => set is in exported SLD vendor option
285  if ( !qgsDoubleNear( contrastEnhancement()->minimumValue(), myRasterBandStats.minimumValue ) )
286  {
287  // look for VendorOption tag to look for that with minValue attribute
288  const QDomNodeList vendorOptions = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
289  for ( int i = 0; i < vendorOptions.size(); ++i )
290  {
291  QDomElement vendorOption = vendorOptions.at( i ).toElement();
292  if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QStringLiteral( "minValue" ) )
293  continue;
294 
295  // remove old value and add the new one
296  vendorOption.removeChild( vendorOption.firstChild() );
297  vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
298  }
299  }
300  break;
301  }
303  break;
305  break;
307  break;
308  }
309 
310  channelElem.appendChild( contrastEnhancementElem );
311  }
312 
313  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
314  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
315  QList< QPair< QString, QColor > > classes;
316  legendSymbologyItems( classes );
317 
318  // add ColorMap tag
319  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
320  rasterSymbolizerElem.appendChild( colorMapElem );
321 
322  // TODO: add clip intervals basing on real min/max without trigger
323  // min/max calculation again that can takes a lot for remote or big images
324  //
325  // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
326  // each ContrastEnhancementAlgorithm need a specific management.
327  // set type of ColorMap ramp [ramp, intervals, values]
328  // basing on interpolation algorithm of the raster shader
329  QList< QPair< QString, QColor > > colorMapping( classes );
330  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
331  {
334  {
335  QString lowValue = classes[0].first;
336  QColor lowColor = classes[0].second;
337  lowColor.setAlpha( 0 );
338  QString highValue = classes[1].first;
339  QColor highColor = classes[1].second;
340  highColor.setAlpha( 0 );
341 
342  colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
343  colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
344  break;
345  }
347  {
348  colorMapping[0].first = QStringLiteral( "0" );
349  colorMapping[1].first = QStringLiteral( "255" );
350  break;
351  }
353  break;
355  break;
356  }
357 
358  // create tags
359  for ( auto it = colorMapping.constBegin(); 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 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
static QgsRasterRenderer * create(const QDomElement &elem, QgsRasterInterface *input)
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.
QgsSingleBandGrayRenderer(QgsRasterInterface *input, int grayBand)
virtual QgsRectangle extent() const
Gets the extent of the interface.
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:315
virtual QgsRasterInterface * input() const
Current input.
DataType
Raster data types.
Definition: qgis.h:101
const QgsContrastEnhancement * contrastEnhancement() const
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:694
void toSld(QDomDocument &doc, QDomElement &element) const
Write ContrastEnhancement tags following SLD v1.0 specs SLD1.0 is limited to the parameters listed in...
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.
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:116
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 _writeXml(QDomDocument &doc, QDomElement &rasterRendererElem) const
Write upper class info into rasterrenderer element (called by writeXml method of subclasses) ...
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.
int alphaValue(double value, int globalTransparency=255) const
Returns the transparency value for a single value pixel.
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:703
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.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
QRgb renderColorForNodataPixel() const
Returns the color for the renderer to use to represent nodata pixels.
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
Feedback object tailored for raster block reading.
Raster renderer pipe that applies colors to a raster.