QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 "qgscolorramplegendnode.h"
23 #include "qgsreadwritecontext.h"
24 #include "qgscolorramp.h"
25 #include "qgssymbol.h"
26 
27 #include <QDomDocument>
28 #include <QDomElement>
29 #include <QImage>
30 #include <QColor>
31 #include <memory>
32 
34  : QgsRasterRenderer( input, QStringLiteral( "singlebandgray" ) )
35  , mGrayBand( grayBand )
36  , mGradient( BlackToWhite )
37  , mContrastEnhancement( nullptr )
38  , mLegendSettings( std::make_unique< QgsColorRampLegendNodeSettings >() )
39 {
40 }
41 
43 {
44  QgsSingleBandGrayRenderer *renderer = new QgsSingleBandGrayRenderer( nullptr, mGrayBand );
45  renderer->copyCommonProperties( this );
46 
47  renderer->setGradient( mGradient );
48  if ( mContrastEnhancement )
49  {
50  renderer->setContrastEnhancement( new QgsContrastEnhancement( *mContrastEnhancement ) );
51  }
52  renderer->setLegendSettings( mLegendSettings ? new QgsColorRampLegendNodeSettings( *mLegendSettings.get() ) : new QgsColorRampLegendNodeSettings() );
53  return renderer;
54 }
55 
57 {
58  if ( elem.isNull() )
59  {
60  return nullptr;
61  }
62 
63  int grayBand = elem.attribute( QStringLiteral( "grayBand" ), QStringLiteral( "-1" ) ).toInt();
65  r->readXml( elem );
66 
67  if ( elem.attribute( QStringLiteral( "gradient" ) ) == QLatin1String( "WhiteToBlack" ) )
68  {
69  r->setGradient( WhiteToBlack ); // BlackToWhite is default
70  }
71 
72  QDomElement contrastEnhancementElem = elem.firstChildElement( QStringLiteral( "contrastEnhancement" ) );
73  if ( !contrastEnhancementElem.isNull() )
74  {
76  input->dataType( grayBand ) ) );
77  ce->readXml( contrastEnhancementElem );
78  r->setContrastEnhancement( ce );
79  }
80 
81  std::unique_ptr< QgsColorRampLegendNodeSettings > legendSettings = std::make_unique< QgsColorRampLegendNodeSettings >();
83  r->setLegendSettings( legendSettings.release() );
84 
85  return r;
86 }
87 
89 {
90  mContrastEnhancement.reset( ce );
91 }
92 
93 QgsRasterBlock *QgsSingleBandGrayRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
94 {
95  Q_UNUSED( bandNo )
96  QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2" ).arg( width ).arg( height ), 4 );
97 
98  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
99  if ( !mInput )
100  {
101  return outputBlock.release();
102  }
103 
104  std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mGrayBand, extent, width, height, feedback ) );
105  if ( !inputBlock || inputBlock->isEmpty() )
106  {
107  QgsDebugMsg( QStringLiteral( "No raster data!" ) );
108  return outputBlock.release();
109  }
110 
111  std::shared_ptr< QgsRasterBlock > alphaBlock;
112 
113  if ( mAlphaBand > 0 && mGrayBand != mAlphaBand )
114  {
115  alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
116  if ( !alphaBlock || alphaBlock->isEmpty() )
117  {
118  // TODO: better to render without alpha
119  return outputBlock.release();
120  }
121  }
122  else if ( mAlphaBand > 0 )
123  {
124  alphaBlock = inputBlock;
125  }
126 
127  if ( !outputBlock->reset( Qgis::DataType::ARGB32_Premultiplied, width, height ) )
128  {
129  return outputBlock.release();
130  }
131 
132  const QRgb myDefaultColor = renderColorForNodataPixel();
133  bool isNoData = false;
134  for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
135  {
136  double grayVal = inputBlock->valueAndNoData( i, isNoData );
137 
138  if ( isNoData )
139  {
140  outputBlock->setColor( i, myDefaultColor );
141  continue;
142  }
143 
144  double currentAlpha = mOpacity;
145  if ( mRasterTransparency )
146  {
147  currentAlpha = mRasterTransparency->alphaValue( grayVal, mOpacity * 255 ) / 255.0;
148  }
149  if ( mAlphaBand > 0 )
150  {
151  currentAlpha *= alphaBlock->value( i ) / 255.0;
152  }
153 
154  if ( mContrastEnhancement )
155  {
156  if ( !mContrastEnhancement->isValueInDisplayableRange( grayVal ) )
157  {
158  outputBlock->setColor( i, myDefaultColor );
159  continue;
160  }
161  grayVal = mContrastEnhancement->enhanceContrast( grayVal );
162  }
163 
164  if ( mGradient == WhiteToBlack )
165  {
166  grayVal = 255 - grayVal;
167  }
168 
169  if ( qgsDoubleNear( currentAlpha, 1.0 ) )
170  {
171  outputBlock->setColor( i, qRgba( grayVal, grayVal, grayVal, 255 ) );
172  }
173  else
174  {
175  outputBlock->setColor( i, qRgba( currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * 255 ) );
176  }
177  }
178 
179  return outputBlock.release();
180 }
181 
182 void QgsSingleBandGrayRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
183 {
184  if ( parentElem.isNull() )
185  {
186  return;
187  }
188 
189  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
190  _writeXml( doc, rasterRendererElem );
191 
192  rasterRendererElem.setAttribute( QStringLiteral( "grayBand" ), mGrayBand );
193 
194  QString gradient;
195  if ( mGradient == BlackToWhite )
196  {
197  gradient = QStringLiteral( "BlackToWhite" );
198  }
199  else
200  {
201  gradient = QStringLiteral( "WhiteToBlack" );
202  }
203  rasterRendererElem.setAttribute( QStringLiteral( "gradient" ), gradient );
204 
205  if ( mContrastEnhancement )
206  {
207  QDomElement contrastElem = doc.createElement( QStringLiteral( "contrastEnhancement" ) );
208  mContrastEnhancement->writeXml( doc, contrastElem );
209  rasterRendererElem.appendChild( contrastElem );
210  }
211 
212  if ( mLegendSettings )
213  mLegendSettings->writeXml( doc, rasterRendererElem, QgsReadWriteContext() );
214 
215  parentElem.appendChild( rasterRendererElem );
216 }
217 
218 QList<QPair<QString, QColor> > QgsSingleBandGrayRenderer::legendSymbologyItems() const
219 {
220  QList<QPair<QString, QColor> > symbolItems;
221  if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
222  {
223  QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
224  QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
225  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->minimumValue() ), minColor ) );
226  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->maximumValue() ), maxColor ) );
227  }
228  return symbolItems;
229 }
230 
231 QList<QgsLayerTreeModelLegendNode *> QgsSingleBandGrayRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer )
232 {
233  QList<QgsLayerTreeModelLegendNode *> res;
234  if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
235  {
236  const QString name = displayBandName( mGrayBand );
237  if ( !name.isEmpty() )
238  {
239  res << new QgsSimpleLegendNode( nodeLayer, name );
240  }
241 
242  const QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
243  const QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
244  res << new QgsColorRampLegendNode( nodeLayer, new QgsGradientColorRamp( minColor, maxColor ),
245  mLegendSettings ? *mLegendSettings : QgsColorRampLegendNodeSettings(),
246  mContrastEnhancement->minimumValue(),
247  mContrastEnhancement->maximumValue() );
248  }
249  return res;
250 }
251 
253 {
254  QList<int> bandList;
255  if ( mGrayBand != -1 )
256  {
257  bandList << mGrayBand;
258  }
259  return bandList;
260 }
261 
262 void QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
263 {
264  // create base structure
265  QgsRasterRenderer::toSld( doc, element, props );
266 
267  // look for RasterSymbolizer tag
268  QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
269  if ( elements.size() == 0 )
270  return;
271 
272  // there SHOULD be only one
273  QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
274 
275  // add Channel Selection tags
276  // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
277  // after opacity or geometry or as first element after sld:RasterSymbolizer
278  QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
279  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
280  if ( elements.size() != 0 )
281  {
282  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
283  }
284  else
285  {
286  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
287  if ( elements.size() != 0 )
288  {
289  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
290  }
291  else
292  {
293  rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
294  }
295  }
296 
297  // for gray band
298  QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
299  channelSelectionElem.appendChild( channelElem );
300 
301  // set band
302  QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
303  sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( grayBand() ) ) );
304  channelElem.appendChild( sourceChannelNameElem );
305 
306  // set ContrastEnhancement
307  if ( auto *lContrastEnhancement = contrastEnhancement() )
308  {
309  QDomElement contrastEnhancementElem = doc.createElement( QStringLiteral( "sld:ContrastEnhancement" ) );
310  lContrastEnhancement->toSld( doc, contrastEnhancementElem );
311 
312  // do changes to minValue/maxValues depending on stretching algorithm. This is necessary because
313  // geoserver does a first stretch on min/max, then applies color map rules.
314  // In some combination it is necessary to use real min/max values and in
315  // others the actual edited min/max values
316  switch ( lContrastEnhancement->contrastEnhancementAlgorithm() )
317  {
320  {
321  // with this renderer export have to be check against real min/max values of the raster
323 
324  // if minimum range differ from the real minimum => set is in exported SLD vendor option
325  if ( !qgsDoubleNear( lContrastEnhancement->minimumValue(), myRasterBandStats.minimumValue ) )
326  {
327  // look for VendorOption tag to look for that with minValue attribute
328  const QDomNodeList vendorOptions = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
329  for ( int i = 0; i < vendorOptions.size(); ++i )
330  {
331  QDomElement vendorOption = vendorOptions.at( i ).toElement();
332  if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QLatin1String( "minValue" ) )
333  continue;
334 
335  // remove old value and add the new one
336  vendorOption.removeChild( vendorOption.firstChild() );
337  vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
338  }
339  }
340  break;
341  }
343  break;
345  break;
347  break;
348  }
349 
350  channelElem.appendChild( contrastEnhancementElem );
351  }
352 
353  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
354  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
355  QList< QPair< QString, QColor > > classes = legendSymbologyItems();
356 
357  // add ColorMap tag
358  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
359  rasterSymbolizerElem.appendChild( colorMapElem );
360 
361  // TODO: add clip intervals basing on real min/max without trigger
362  // min/max calculation again that can takes a lot for remote or big images
363  //
364  // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
365  // each ContrastEnhancementAlgorithm need a specific management.
366  // set type of ColorMap ramp [ramp, intervals, values]
367  // basing on interpolation algorithm of the raster shader
368  QList< QPair< QString, QColor > > colorMapping( classes );
369  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
370  {
373  {
374  QString lowValue = classes[0].first;
375  QColor lowColor = classes[0].second;
376  lowColor.setAlpha( 0 );
377  QString highValue = classes[1].first;
378  QColor highColor = classes[1].second;
379  highColor.setAlpha( 0 );
380 
381  colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
382  colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
383  break;
384  }
386  {
387  colorMapping[0].first = QStringLiteral( "0" );
388  colorMapping[1].first = QStringLiteral( "255" );
389  break;
390  }
392  break;
394  break;
395  }
396 
397  // create tags
398  for ( auto it = colorMapping.constBegin(); it != colorMapping.constEnd() ; ++it )
399  {
400  // set low level color mapping
401  QDomElement lowColorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
402  colorMapElem.appendChild( lowColorMapEntryElem );
403  lowColorMapEntryElem.setAttribute( QStringLiteral( "color" ), it->second.name() );
404  lowColorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), it->first );
405  if ( it->second.alphaF() == 0.0 )
406  {
407  lowColorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( it->second.alpha() ) );
408  }
409  }
410 }
411 
413 {
414  return mLegendSettings.get();
415 }
416 
418 {
419  if ( settings == mLegendSettings.get() )
420  return;
421  mLegendSettings.reset( settings );
422 }
DataType
Raster data types.
Definition: qgis.h:119
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Settings for a color ramp legend node.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads settings from an XML element.
A legend node which renders a color ramp.
Manipulates raster or point cloud pixel values so that they enhanceContrast or clip into a specified ...
@ StretchToMinimumMaximum
Linear histogram.
@ NoEnhancement
Default color scaling algorithm, no scaling is applied.
void readXml(const QDomElement &elem)
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:151
Layer tree node points to a map layer.
The RasterBandStats struct is a container for statistics about a single raster band.
double minimumValue
The minimum cell value in the raster band.
Feedback object tailored for raster block reading.
Raster data container.
Base class for processing filters like renderers, reprojector, resampler etc.
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.
virtual Qgis::DataType dataType(int bandNo) const =0
Returns data type for the band specified by number.
virtual QgsRasterInterface * input() const
Current input.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
QString displayBandName(int bandNumber) const
Generates a friendly, descriptive name for the specified bandNumber.
QgsRasterInterface * mInput
virtual QgsRectangle extent() const
Gets the extent of the interface.
Raster renderer pipe that applies colors to a raster.
double mOpacity
Global alpha value (0-1)
int mAlphaBand
Read alpha value from band.
QRgb renderColorForNodataPixel() const
Returns the color for the renderer to use to represent nodata pixels.
void _writeXml(QDomDocument &doc, QDomElement &rasterRendererElem) const
Write upper class info into rasterrenderer element (called by writeXml method of subclasses)
QgsRasterTransparency * mRasterTransparency
Raster transparency per color or value. Overwrites global alpha value.
void copyCommonProperties(const QgsRasterRenderer *other, bool copyMinMaxOrigin=true)
Copies common properties like opacity / transparency data from other renderer.
virtual void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props=QVariantMap()) const
Used from subclasses to create SLD Rule elements following SLD v1.0 specs.
void readXml(const QDomElement &rendererElem) override
Sets base class members from xml. Usually called from create() methods of subclasses.
int alphaValue(double value, int globalTransparency=255) const
Returns the transparency value for a single value pixel.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Implementation of legend node interface for displaying arbitrary label with icon.
Raster renderer pipe for single band gray.
void setLegendSettings(QgsColorRampLegendNodeSettings *settings)
Sets the color ramp shader legend settings.
QList< int > usesBands() const override
Returns a list of band numbers used by the renderer.
void setContrastEnhancement(QgsContrastEnhancement *ce)
Takes ownership.
void writeXml(QDomDocument &doc, QDomElement &parentElem) const override
Write base class members to xml.
static QgsRasterRenderer * create(const QDomElement &elem, QgsRasterInterface *input)
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props=QVariantMap()) const override
Used from subclasses to create SLD Rule elements following SLD v1.0 specs.
const QgsContrastEnhancement * contrastEnhancement() const
QgsSingleBandGrayRenderer(QgsRasterInterface *input, int grayBand)
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
const QgsColorRampLegendNodeSettings * legendSettings() const
Returns the color ramp shader legend settings.
QList< QPair< QString, QColor > > legendSymbologyItems() const override
Returns symbology items if provided by renderer.
QList< QgsLayerTreeModelLegendNode * > createLegendNodes(QgsLayerTreeLayer *nodeLayer) override
Creates a set of legend nodes representing the renderer.
void setGradient(Gradient gradient)
QgsSingleBandGrayRenderer * clone() const override
Clone itself, create deep copy.
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:1051
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38