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