QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 <QDomDocument>
25 #include <QDomElement>
26 #include <QImage>
27 #include <QColor>
28 #include <memory>
29 
31  : QgsRasterRenderer( input, QStringLiteral( "singlebandgray" ) )
32  , mGrayBand( grayBand )
33  , mGradient( BlackToWhite )
34  , mContrastEnhancement( nullptr )
35  , mLegendSettings( qgis::make_unique< QgsColorRampLegendNodeSettings >() )
36 {
37 }
38 
40 {
41  QgsSingleBandGrayRenderer *renderer = new QgsSingleBandGrayRenderer( nullptr, mGrayBand );
42  renderer->copyCommonProperties( this );
43 
44  renderer->setGradient( mGradient );
45  if ( mContrastEnhancement )
46  {
47  renderer->setContrastEnhancement( new QgsContrastEnhancement( *mContrastEnhancement ) );
48  }
49  renderer->setLegendSettings( mLegendSettings ? new QgsColorRampLegendNodeSettings( *mLegendSettings.get() ) : new QgsColorRampLegendNodeSettings() );
50  return renderer;
51 }
52 
54 {
55  if ( elem.isNull() )
56  {
57  return nullptr;
58  }
59 
60  int grayBand = elem.attribute( QStringLiteral( "grayBand" ), QStringLiteral( "-1" ) ).toInt();
62  r->readXml( elem );
63 
64  if ( elem.attribute( QStringLiteral( "gradient" ) ) == QLatin1String( "WhiteToBlack" ) )
65  {
66  r->setGradient( WhiteToBlack ); // BlackToWhite is default
67  }
68 
69  QDomElement contrastEnhancementElem = elem.firstChildElement( QStringLiteral( "contrastEnhancement" ) );
70  if ( !contrastEnhancementElem.isNull() )
71  {
73  input->dataType( grayBand ) ) );
74  ce->readXml( contrastEnhancementElem );
75  r->setContrastEnhancement( ce );
76  }
77 
78  std::unique_ptr< QgsColorRampLegendNodeSettings > legendSettings = qgis::make_unique< QgsColorRampLegendNodeSettings >();
80  r->setLegendSettings( legendSettings.release() );
81 
82  return r;
83 }
84 
86 {
87  mContrastEnhancement.reset( ce );
88 }
89 
90 QgsRasterBlock *QgsSingleBandGrayRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
91 {
92  Q_UNUSED( bandNo )
93  QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2" ).arg( width ).arg( height ), 4 );
94 
95  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
96  if ( !mInput )
97  {
98  return outputBlock.release();
99  }
100 
101  std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mGrayBand, extent, width, height, feedback ) );
102  if ( !inputBlock || inputBlock->isEmpty() )
103  {
104  QgsDebugMsg( QStringLiteral( "No raster data!" ) );
105  return outputBlock.release();
106  }
107 
108  std::shared_ptr< QgsRasterBlock > alphaBlock;
109 
110  if ( mAlphaBand > 0 && mGrayBand != mAlphaBand )
111  {
112  alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
113  if ( !alphaBlock || alphaBlock->isEmpty() )
114  {
115  // TODO: better to render without alpha
116  return outputBlock.release();
117  }
118  }
119  else if ( mAlphaBand > 0 )
120  {
121  alphaBlock = inputBlock;
122  }
123 
124  if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
125  {
126  return outputBlock.release();
127  }
128 
129  const QRgb myDefaultColor = renderColorForNodataPixel();
130  bool isNoData = false;
131  for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
132  {
133  double grayVal = inputBlock->valueAndNoData( i, isNoData );
134 
135  if ( isNoData )
136  {
137  outputBlock->setColor( i, myDefaultColor );
138  continue;
139  }
140 
141  double currentAlpha = mOpacity;
142  if ( mRasterTransparency )
143  {
144  currentAlpha = mRasterTransparency->alphaValue( grayVal, mOpacity * 255 ) / 255.0;
145  }
146  if ( mAlphaBand > 0 )
147  {
148  currentAlpha *= alphaBlock->value( i ) / 255.0;
149  }
150 
151  if ( mContrastEnhancement )
152  {
153  if ( !mContrastEnhancement->isValueInDisplayableRange( grayVal ) )
154  {
155  outputBlock->setColor( i, myDefaultColor );
156  continue;
157  }
158  grayVal = mContrastEnhancement->enhanceContrast( grayVal );
159  }
160 
161  if ( mGradient == WhiteToBlack )
162  {
163  grayVal = 255 - grayVal;
164  }
165 
166  if ( qgsDoubleNear( currentAlpha, 1.0 ) )
167  {
168  outputBlock->setColor( i, qRgba( grayVal, grayVal, grayVal, 255 ) );
169  }
170  else
171  {
172  outputBlock->setColor( i, qRgba( currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * 255 ) );
173  }
174  }
175 
176  return outputBlock.release();
177 }
178 
179 void QgsSingleBandGrayRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
180 {
181  if ( parentElem.isNull() )
182  {
183  return;
184  }
185 
186  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
187  _writeXml( doc, rasterRendererElem );
188 
189  rasterRendererElem.setAttribute( QStringLiteral( "grayBand" ), mGrayBand );
190 
191  QString gradient;
192  if ( mGradient == BlackToWhite )
193  {
194  gradient = QStringLiteral( "BlackToWhite" );
195  }
196  else
197  {
198  gradient = QStringLiteral( "WhiteToBlack" );
199  }
200  rasterRendererElem.setAttribute( QStringLiteral( "gradient" ), gradient );
201 
202  if ( mContrastEnhancement )
203  {
204  QDomElement contrastElem = doc.createElement( QStringLiteral( "contrastEnhancement" ) );
205  mContrastEnhancement->writeXml( doc, contrastElem );
206  rasterRendererElem.appendChild( contrastElem );
207  }
208 
209  if ( mLegendSettings )
210  mLegendSettings->writeXml( doc, rasterRendererElem, QgsReadWriteContext() );
211 
212  parentElem.appendChild( rasterRendererElem );
213 }
214 
215 QList<QPair<QString, QColor> > QgsSingleBandGrayRenderer::legendSymbologyItems() const
216 {
217  QList<QPair<QString, QColor> > symbolItems;
218  if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
219  {
220  QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
221  QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
222  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->minimumValue() ), minColor ) );
223  symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->maximumValue() ), maxColor ) );
224  }
225  return symbolItems;
226 }
227 
228 QList<QgsLayerTreeModelLegendNode *> QgsSingleBandGrayRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer )
229 {
230  QList<QgsLayerTreeModelLegendNode *> res;
231  if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
232  {
233  const QString name = displayBandName( mGrayBand );
234  if ( !name.isEmpty() )
235  {
236  res << new QgsSimpleLegendNode( nodeLayer, name );
237  }
238 
239  const QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
240  const QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
241  res << new QgsColorRampLegendNode( nodeLayer, new QgsGradientColorRamp( minColor, maxColor ),
242  mLegendSettings ? *mLegendSettings : QgsColorRampLegendNodeSettings(),
243  mContrastEnhancement->minimumValue(),
244  mContrastEnhancement->maximumValue() );
245  }
246  return res;
247 }
248 
250 {
251  QList<int> bandList;
252  if ( mGrayBand != -1 )
253  {
254  bandList << mGrayBand;
255  }
256  return bandList;
257 }
258 
259 void QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
260 {
261  // create base structure
262  QgsRasterRenderer::toSld( doc, element, props );
263 
264  // look for RasterSymbolizer tag
265  QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
266  if ( elements.size() == 0 )
267  return;
268 
269  // there SHOULD be only one
270  QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
271 
272  // add Channel Selection tags
273  // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
274  // after opacity or geometry or as first element after sld:RasterSymbolizer
275  QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
276  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
277  if ( elements.size() != 0 )
278  {
279  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
280  }
281  else
282  {
283  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
284  if ( elements.size() != 0 )
285  {
286  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
287  }
288  else
289  {
290  rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
291  }
292  }
293 
294  // for gray band
295  QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
296  channelSelectionElem.appendChild( channelElem );
297 
298  // set band
299  QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
300  sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( grayBand() ) ) );
301  channelElem.appendChild( sourceChannelNameElem );
302 
303  // set ContrastEnhancement
304  if ( auto *lContrastEnhancement = contrastEnhancement() )
305  {
306  QDomElement contrastEnhancementElem = doc.createElement( QStringLiteral( "sld:ContrastEnhancement" ) );
307  lContrastEnhancement->toSld( doc, contrastEnhancementElem );
308 
309  // do changes to minValue/maxValues depending on stretching algorithm. This is necessary because
310  // geoserver does a first stretch on min/max, then applies color map rules.
311  // In some combination it is necessary to use real min/max values and in
312  // others the actual edited min/max values
313  switch ( lContrastEnhancement->contrastEnhancementAlgorithm() )
314  {
317  {
318  // with this renderer export have to be check against real min/max values of the raster
320 
321  // if minimum range differ from the real minimum => set is in exported SLD vendor option
322  if ( !qgsDoubleNear( lContrastEnhancement->minimumValue(), myRasterBandStats.minimumValue ) )
323  {
324  // look for VendorOption tag to look for that with minValue attribute
325  const QDomNodeList vendorOptions = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
326  for ( int i = 0; i < vendorOptions.size(); ++i )
327  {
328  QDomElement vendorOption = vendorOptions.at( i ).toElement();
329  if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QLatin1String( "minValue" ) )
330  continue;
331 
332  // remove old value and add the new one
333  vendorOption.removeChild( vendorOption.firstChild() );
334  vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
335  }
336  }
337  break;
338  }
340  break;
342  break;
344  break;
345  }
346 
347  channelElem.appendChild( contrastEnhancementElem );
348  }
349 
350  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
351  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
352  QList< QPair< QString, QColor > > classes = legendSymbologyItems();
353 
354  // add ColorMap tag
355  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
356  rasterSymbolizerElem.appendChild( colorMapElem );
357 
358  // TODO: add clip intervals basing on real min/max without trigger
359  // min/max calculation again that can takes a lot for remote or big images
360  //
361  // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
362  // each ContrastEnhancementAlgorithm need a specific management.
363  // set type of ColorMap ramp [ramp, intervals, values]
364  // basing on interpolation algorithm of the raster shader
365  QList< QPair< QString, QColor > > colorMapping( classes );
366  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
367  {
370  {
371  QString lowValue = classes[0].first;
372  QColor lowColor = classes[0].second;
373  lowColor.setAlpha( 0 );
374  QString highValue = classes[1].first;
375  QColor highColor = classes[1].second;
376  highColor.setAlpha( 0 );
377 
378  colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
379  colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
380  break;
381  }
383  {
384  colorMapping[0].first = QStringLiteral( "0" );
385  colorMapping[1].first = QStringLiteral( "255" );
386  break;
387  }
389  break;
391  break;
392  }
393 
394  // create tags
395  for ( auto it = colorMapping.constBegin(); it != colorMapping.constEnd() ; ++it )
396  {
397  // set low level color mapping
398  QDomElement lowColorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
399  colorMapElem.appendChild( lowColorMapEntryElem );
400  lowColorMapEntryElem.setAttribute( QStringLiteral( "color" ), it->second.name() );
401  lowColorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), it->first );
402  if ( it->second.alphaF() == 0.0 )
403  {
404  lowColorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( it->second.alpha() ) );
405  }
406  }
407 }
408 
410 {
411  return mLegendSettings.get();
412 }
413 
415 {
416  if ( settings == mLegendSettings.get() )
417  return;
418  mLegendSettings.reset( settings );
419 }
DataType
Raster data types.
Definition: qgis.h:102
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:116
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:769
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38