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