QGIS API Documentation  3.6.0-Noosa (5873452)
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  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  QgsStringMap newProps = props;
225 
226  // create base structure
227  QgsRasterRenderer::toSld( doc, element, props );
228 
229  // look for RasterSymbolizer tag
230  QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
231  if ( elements.size() == 0 )
232  return;
233 
234  // there SHOULD be only one
235  QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
236 
237  // add Channel Selection tags
238  // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
239  // after opacity or geometry or as first element after sld:RasterSymbolizer
240  QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
241  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
242  if ( elements.size() != 0 )
243  {
244  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
245  }
246  else
247  {
248  elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
249  if ( elements.size() != 0 )
250  {
251  rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
252  }
253  else
254  {
255  rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
256  }
257  }
258 
259  // for gray band
260  QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
261  channelSelectionElem.appendChild( channelElem );
262 
263  // set band
264  QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
265  sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( grayBand() ) ) );
266  channelElem.appendChild( sourceChannelNameElem );
267 
268  // set ContrastEnhancement
269  if ( contrastEnhancement() )
270  {
271  QDomElement contrastEnhancementElem = doc.createElement( QStringLiteral( "sld:ContrastEnhancement" ) );
272  contrastEnhancement()->toSld( doc, contrastEnhancementElem );
273 
274  // do changes to minValue/maxValues depending on stretching algorithm. This is necessary because
275  // geoserver do a first stretch on min/max, then apply colo map rules. In some combination is necessary
276  // to use real min/max values and in othere the actual edited min/max values
277  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
278  {
281  {
282  // with this renderer export have to be check against real min/max values of the raster
284 
285  // if minimum range differ from the real minimum => set is in exported SLD vendor option
286  if ( !qgsDoubleNear( contrastEnhancement()->minimumValue(), myRasterBandStats.minimumValue ) )
287  {
288  // look for VendorOption tag to look for that with minValue attribute
289  QDomNodeList elements = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
290  for ( int i = 0; i < elements.size(); ++i )
291  {
292  QDomElement vendorOption = elements.at( i ).toElement();
293  if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QStringLiteral( "minValue" ) )
294  continue;
295 
296  // remove old value and add the new one
297  vendorOption.removeChild( vendorOption.firstChild() );
298  vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
299  }
300  }
301  break;
302  }
304  break;
306  break;
308  break;
309  }
310 
311  channelElem.appendChild( contrastEnhancementElem );
312  }
313 
314  // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
315  // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
316  QList< QPair< QString, QColor > > classes;
317  legendSymbologyItems( classes );
318 
319  // add ColorMap tag
320  QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
321  rasterSymbolizerElem.appendChild( colorMapElem );
322 
323  // TODO: add clip intervals basing on real min/max without trigger
324  // min/max calculation again that can takes a lot for remote or big images
325  //
326  // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
327  // each ContrastEnhancementAlgorithm need a specific management.
328  // set type of ColorMap ramp [ramp, intervals, values]
329  // basing on interpolation algorithm of the raster shader
330  QList< QPair< QString, QColor > > colorMapping( classes );
331  switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
332  {
335  {
336  QString lowValue = classes[0].first;
337  QColor lowColor = classes[0].second;
338  lowColor.setAlpha( 0 );
339  QString highValue = classes[1].first;
340  QColor highColor = classes[1].second;
341  highColor.setAlpha( 0 );
342 
343  colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
344  colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
345  break;
346  }
348  {
349  colorMapping[0].first = QStringLiteral( "0" );
350  colorMapping[1].first = QStringLiteral( "255" );
351  break;
352  }
354  break;
356  break;
357  }
358 
359  // create tags
360  QList< QPair< QString, QColor > >::ConstIterator it;
361  for ( it = colorMapping.begin(); it != colorMapping.constEnd() ; ++it )
362  {
363  // set low level color mapping
364  QDomElement lowColorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
365  colorMapElem.appendChild( lowColorMapEntryElem );
366  lowColorMapEntryElem.setAttribute( QStringLiteral( "color" ), it->second.name() );
367  lowColorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), it->first );
368  if ( it->second.alphaF() == 0.0 )
369  {
370  lowColorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( it->second.alpha() ) );
371  }
372  }
373 }
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:265
virtual QgsRasterInterface * input() const
Current input.
DataType
Raster data types.
Definition: qgis.h:79
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:587
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.
static const QRgb NODATA_COLOR
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:94
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:596
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.
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.