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