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