QGIS API Documentation 3.43.0-Master (56aa1fd18d7)
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#include "qgssldexportcontext.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
60
62{
63 if ( elem.isNull() )
64 {
65 return nullptr;
66 }
67
68 const int grayBand = elem.attribute( QStringLiteral( "grayBand" ), QStringLiteral( "-1" ) ).toInt();
70 r->readXml( elem );
71
72 if ( elem.attribute( QStringLiteral( "gradient" ) ) == QLatin1String( "WhiteToBlack" ) )
73 {
74 r->setGradient( WhiteToBlack ); // BlackToWhite is default
75 }
76
77 const QDomElement contrastEnhancementElem = elem.firstChildElement( QStringLiteral( "contrastEnhancement" ) );
78 if ( !contrastEnhancementElem.isNull() )
79 {
81 input->dataType( grayBand ) ) );
82 ce->readXml( contrastEnhancementElem );
84 }
85
86 auto legendSettings = std::make_unique< QgsColorRampLegendNodeSettings >();
88 r->setLegendSettings( legendSettings.release() );
89
90 return r;
91}
92
94{
95 mContrastEnhancement.reset( ce );
96}
97
98QgsRasterBlock *QgsSingleBandGrayRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
99{
100 Q_UNUSED( bandNo )
101 QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2" ).arg( width ).arg( height ), 4 );
102
103 auto outputBlock = std::make_unique<QgsRasterBlock>();
104 if ( !mInput )
105 {
106 return outputBlock.release();
107 }
108
109 const std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mGrayBand, extent, width, height, feedback ) );
110 if ( !inputBlock || inputBlock->isEmpty() )
111 {
112 QgsDebugError( QStringLiteral( "No raster data!" ) );
113 return outputBlock.release();
114 }
115
116 std::shared_ptr< QgsRasterBlock > alphaBlock;
117
118 if ( mAlphaBand > 0 && mGrayBand != mAlphaBand )
119 {
120 alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
121 if ( !alphaBlock || alphaBlock->isEmpty() )
122 {
123 // TODO: better to render without alpha
124 return outputBlock.release();
125 }
126 }
127 else if ( mAlphaBand > 0 )
128 {
129 alphaBlock = inputBlock;
130 }
131
132 if ( !outputBlock->reset( Qgis::DataType::ARGB32_Premultiplied, width, height ) )
133 {
134 return outputBlock.release();
135 }
136
137 const QRgb myDefaultColor = renderColorForNodataPixel();
138 bool isNoData = false;
139 for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
140 {
141 double grayVal = inputBlock->valueAndNoData( i, isNoData );
142
143 if ( isNoData )
144 {
145 outputBlock->setColor( i, myDefaultColor );
146 continue;
147 }
148
149 double currentAlpha = mOpacity;
151 {
152 currentAlpha *= mRasterTransparency->opacityForValue( grayVal );
153 }
154 if ( mAlphaBand > 0 )
155 {
156 const double alpha = alphaBlock->value( i );
157 if ( alpha == 0 )
158 {
159 outputBlock->setColor( i, myDefaultColor );
160 continue;
161 }
162 else
163 {
164 currentAlpha *= alpha / 255.0;
165 }
166 }
167
168 if ( mContrastEnhancement )
169 {
170 if ( !mContrastEnhancement->isValueInDisplayableRange( grayVal ) )
171 {
172 outputBlock->setColor( i, myDefaultColor );
173 continue;
174 }
175 grayVal = mContrastEnhancement->enhanceContrast( grayVal );
176 }
177
178 if ( mGradient == WhiteToBlack )
179 {
180 grayVal = 255 - grayVal;
181 }
182
183 if ( qgsDoubleNear( currentAlpha, 1.0 ) )
184 {
185 outputBlock->setColor( i, qRgba( grayVal, grayVal, grayVal, 255 ) );
186 }
187 else
188 {
189 outputBlock->setColor( i, qRgba( currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * 255 ) );
190 }
191 }
192
193 return outputBlock.release();
194}
195
197{
198 setInputBand( band );
199}
200
202{
203 return mGrayBand;
204}
205
207{
208 if ( !mInput )
209 {
210 mGrayBand = band;
211 return true;
212 }
213 else if ( band > 0 && band <= mInput->bandCount() )
214 {
215 mGrayBand = band;
216 return true;
217 }
218 return false;
219}
220
221void QgsSingleBandGrayRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
222{
223 if ( parentElem.isNull() )
224 {
225 return;
226 }
227
228 QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
229 _writeXml( doc, rasterRendererElem );
230
231 rasterRendererElem.setAttribute( QStringLiteral( "grayBand" ), mGrayBand );
232
233 QString gradient;
234 if ( mGradient == BlackToWhite )
235 {
236 gradient = QStringLiteral( "BlackToWhite" );
237 }
238 else
239 {
240 gradient = QStringLiteral( "WhiteToBlack" );
241 }
242 rasterRendererElem.setAttribute( QStringLiteral( "gradient" ), gradient );
243
244 if ( mContrastEnhancement )
245 {
246 QDomElement contrastElem = doc.createElement( QStringLiteral( "contrastEnhancement" ) );
247 mContrastEnhancement->writeXml( doc, contrastElem );
248 rasterRendererElem.appendChild( contrastElem );
249 }
250
251 if ( mLegendSettings )
252 mLegendSettings->writeXml( doc, rasterRendererElem, QgsReadWriteContext() );
253
254 parentElem.appendChild( rasterRendererElem );
255}
256
257QList<QPair<QString, QColor> > QgsSingleBandGrayRenderer::legendSymbologyItems() const
258{
259 QList<QPair<QString, QColor> > symbolItems;
260 if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
261 {
262 const QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
263 const QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
264 symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->minimumValue() ), minColor ) );
265 symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->maximumValue() ), maxColor ) );
266 }
267 return symbolItems;
268}
269
270QList<QgsLayerTreeModelLegendNode *> QgsSingleBandGrayRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer )
271{
272 QList<QgsLayerTreeModelLegendNode *> res;
273 if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
274 {
275 const QString name = displayBandName( mGrayBand );
276 if ( !name.isEmpty() )
277 {
278 res << new QgsSimpleLegendNode( nodeLayer, name );
279 }
280
281 const QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
282 const QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
283 res << new QgsColorRampLegendNode( nodeLayer, new QgsGradientColorRamp( minColor, maxColor ),
284 mLegendSettings ? *mLegendSettings : QgsColorRampLegendNodeSettings(),
285 mContrastEnhancement->minimumValue(),
286 mContrastEnhancement->maximumValue() );
287 }
288 return res;
289}
290
292{
293 QList<int> bandList;
294 if ( mGrayBand != -1 )
295 {
296 bandList << mGrayBand;
297 }
298 return bandList;
299}
300
301void QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
302{
303 QgsSldExportContext context;
304 context.setExtraProperties( props );
305 toSld( doc, element, context );
306}
307
308bool QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
309{
310 // create base structure
311 QgsRasterRenderer::toSld( doc, element, context );
312
313 // look for RasterSymbolizer tag
314 QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
315 if ( elements.size() == 0 )
316 return false;
317
318 // there SHOULD be only one
319 QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
320
321 // add Channel Selection tags
322 // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
323 // after opacity or geometry or as first element after sld:RasterSymbolizer
324 QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
325 elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
326 if ( elements.size() != 0 )
327 {
328 rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
329 }
330 else
331 {
332 elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
333 if ( elements.size() != 0 )
334 {
335 rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
336 }
337 else
338 {
339 rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
340 }
341 }
342
343 // for gray band
344 QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
345 channelSelectionElem.appendChild( channelElem );
346
347 // set band
348 QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
349 sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( mGrayBand ) ) );
350 channelElem.appendChild( sourceChannelNameElem );
351
352 // set ContrastEnhancement
353 if ( auto *lContrastEnhancement = contrastEnhancement() )
354 {
355 QDomElement contrastEnhancementElem = doc.createElement( QStringLiteral( "sld:ContrastEnhancement" ) );
356 lContrastEnhancement->toSld( doc, contrastEnhancementElem );
357
358 // do changes to minValue/maxValues depending on stretching algorithm. This is necessary because
359 // geoserver does a first stretch on min/max, then applies color map rules.
360 // In some combination it is necessary to use real min/max values and in
361 // others the actual edited min/max values
362 switch ( lContrastEnhancement->contrastEnhancementAlgorithm() )
363 {
366 {
367 // with this renderer export have to be check against real min/max values of the raster
369
370 // if minimum range differ from the real minimum => set is in exported SLD vendor option
371 if ( !qgsDoubleNear( lContrastEnhancement->minimumValue(), myRasterBandStats.minimumValue ) )
372 {
373 // look for VendorOption tag to look for that with minValue attribute
374 const QDomNodeList vendorOptions = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
375 for ( int i = 0; i < vendorOptions.size(); ++i )
376 {
377 QDomElement vendorOption = vendorOptions.at( i ).toElement();
378 if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QLatin1String( "minValue" ) )
379 continue;
380
381 // remove old value and add the new one
382 vendorOption.removeChild( vendorOption.firstChild() );
383 vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
384 }
385 }
386 break;
387 }
389 break;
391 break;
393 break;
394 }
395
396 channelElem.appendChild( contrastEnhancementElem );
397 }
398
399 // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
400 // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
401 QList< QPair< QString, QColor > > classes = legendSymbologyItems();
402
403 // add ColorMap tag
404 QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
405 rasterSymbolizerElem.appendChild( colorMapElem );
406
407 // TODO: add clip intervals basing on real min/max without trigger
408 // min/max calculation again that can takes a lot for remote or big images
409 //
410 // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
411 // each ContrastEnhancementAlgorithm need a specific management.
412 // set type of ColorMap ramp [ramp, intervals, values]
413 // basing on interpolation algorithm of the raster shader
414 QList< QPair< QString, QColor > > colorMapping( classes );
415 switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
416 {
419 {
420 const QString lowValue = classes[0].first;
421 QColor lowColor = classes[0].second;
422 lowColor.setAlpha( 0 );
423 const QString highValue = classes[1].first;
424 QColor highColor = classes[1].second;
425 highColor.setAlpha( 0 );
426
427 colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
428 colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
429 break;
430 }
432 {
433 colorMapping[0].first = QStringLiteral( "0" );
434 colorMapping[1].first = QStringLiteral( "255" );
435 break;
436 }
438 break;
440 break;
441 }
442
443 // create tags
444 for ( auto it = colorMapping.constBegin(); it != colorMapping.constEnd() ; ++it )
445 {
446 // set low level color mapping
447 QDomElement lowColorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
448 colorMapElem.appendChild( lowColorMapEntryElem );
449 lowColorMapEntryElem.setAttribute( QStringLiteral( "color" ), it->second.name() );
450 lowColorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), it->first );
451 if ( it->second.alphaF() == 0.0 )
452 {
453 lowColorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( it->second.alpha() ) );
454 }
455 }
456 return true;
457}
458
460{
461 return mLegendSettings.get();
462}
463
465{
466 if ( settings == mLegendSettings.get() )
467 return;
468 mLegendSettings.reset( settings );
469}
470
471bool QgsSingleBandGrayRenderer::refresh( const QgsRectangle &extent, const QList<double> &min, const QList<double> &max, bool forceRefresh )
472{
473 if ( !needsRefresh( extent ) && !forceRefresh )
474 {
475 return false;
476 }
477
478 bool refreshed = false;
479 if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement &&
480 min.size() >= 1 && max.size() >= 1 )
481 {
483 mContrastEnhancement->setMinimumValue( min[0] );
484 mContrastEnhancement->setMaximumValue( max[0] );
485 refreshed = true;
486 }
487
488 return refreshed;
489}
QFlags< RasterRendererFlag > RasterRendererFlags
Flags which control behavior of raster renderers.
Definition qgis.h:1475
@ InternalLayerOpacityHandling
The renderer internally handles the raster layer's opacity, so the default layer level opacity handli...
DataType
Raster data types.
Definition qgis.h:351
@ 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.
Handles contrast enhancement and clipping.
@ 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.
QgsRectangle mLastRectangleUsedByRefreshContrastEnhancementIfNeeded
To save computations and possible infinite cycle of notifications.
QRgb renderColorForNodataPixel() const
Returns the color for the renderer to use to represent nodata pixels.
std::unique_ptr< QgsRasterTransparency > mRasterTransparency
Raster transparency per color or value. Overwrites global alpha value.
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.
void copyCommonProperties(const QgsRasterRenderer *other, bool copyMinMaxOrigin=true)
Copies common properties like opacity / transparency data from other renderer.
bool needsRefresh(const QgsRectangle &extent) const
Checks if the renderer needs to be refreshed according to extent.
virtual Q_DECL_DEPRECATED 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.
A container for the context for various read/write operations on objects.
A rectangle specified with double values.
Implementation of legend node interface for displaying arbitrary labels with icons.
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)
Q_DECL_DEPRECATED 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.
bool refresh(const QgsRectangle &extent, const QList< double > &min, const QList< double > &max, bool forceRefresh=false) override
Refreshes the renderer according to the min and max values associated with the extent.
QgsSingleBandGrayRenderer * clone() const override
Clone itself, create deep copy.
Holds SLD export options and other information related to SLD export of a QGIS layer style.
void setExtraProperties(const QVariantMap &properties)
Sets the open ended set of properties that can drive/inform the SLD encoding.
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:6826
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6302
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40