QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 "qgssymbol.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
56Qgis::RasterRendererFlags QgsSingleBandGrayRenderer::flags() const
57{
59}
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 std::unique_ptr< QgsColorRampLegendNodeSettings > 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 std::unique_ptr< QgsRasterBlock > outputBlock( new 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 QgsDebugMsg( 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->alphaValue( grayVal, mOpacity * 255 ) / 255.0;
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
196void QgsSingleBandGrayRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
197{
198 if ( parentElem.isNull() )
199 {
200 return;
201 }
202
203 QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
204 _writeXml( doc, rasterRendererElem );
205
206 rasterRendererElem.setAttribute( QStringLiteral( "grayBand" ), mGrayBand );
207
208 QString gradient;
209 if ( mGradient == BlackToWhite )
210 {
211 gradient = QStringLiteral( "BlackToWhite" );
212 }
213 else
214 {
215 gradient = QStringLiteral( "WhiteToBlack" );
216 }
217 rasterRendererElem.setAttribute( QStringLiteral( "gradient" ), gradient );
218
219 if ( mContrastEnhancement )
220 {
221 QDomElement contrastElem = doc.createElement( QStringLiteral( "contrastEnhancement" ) );
222 mContrastEnhancement->writeXml( doc, contrastElem );
223 rasterRendererElem.appendChild( contrastElem );
224 }
225
226 if ( mLegendSettings )
227 mLegendSettings->writeXml( doc, rasterRendererElem, QgsReadWriteContext() );
228
229 parentElem.appendChild( rasterRendererElem );
230}
231
232QList<QPair<QString, QColor> > QgsSingleBandGrayRenderer::legendSymbologyItems() const
233{
234 QList<QPair<QString, QColor> > symbolItems;
235 if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
236 {
237 const QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
238 const QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
239 symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->minimumValue() ), minColor ) );
240 symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->maximumValue() ), maxColor ) );
241 }
242 return symbolItems;
243}
244
245QList<QgsLayerTreeModelLegendNode *> QgsSingleBandGrayRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer )
246{
247 QList<QgsLayerTreeModelLegendNode *> res;
248 if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
249 {
250 const QString name = displayBandName( mGrayBand );
251 if ( !name.isEmpty() )
252 {
253 res << new QgsSimpleLegendNode( nodeLayer, name );
254 }
255
256 const QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
257 const QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
258 res << new QgsColorRampLegendNode( nodeLayer, new QgsGradientColorRamp( minColor, maxColor ),
259 mLegendSettings ? *mLegendSettings : QgsColorRampLegendNodeSettings(),
260 mContrastEnhancement->minimumValue(),
261 mContrastEnhancement->maximumValue() );
262 }
263 return res;
264}
265
267{
268 QList<int> bandList;
269 if ( mGrayBand != -1 )
270 {
271 bandList << mGrayBand;
272 }
273 return bandList;
274}
275
276void QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
277{
278 // create base structure
279 QgsRasterRenderer::toSld( doc, element, props );
280
281 // look for RasterSymbolizer tag
282 QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
283 if ( elements.size() == 0 )
284 return;
285
286 // there SHOULD be only one
287 QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
288
289 // add Channel Selection tags
290 // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
291 // after opacity or geometry or as first element after sld:RasterSymbolizer
292 QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
293 elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
294 if ( elements.size() != 0 )
295 {
296 rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
297 }
298 else
299 {
300 elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
301 if ( elements.size() != 0 )
302 {
303 rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
304 }
305 else
306 {
307 rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
308 }
309 }
310
311 // for gray band
312 QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
313 channelSelectionElem.appendChild( channelElem );
314
315 // set band
316 QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
317 sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( grayBand() ) ) );
318 channelElem.appendChild( sourceChannelNameElem );
319
320 // set ContrastEnhancement
321 if ( auto *lContrastEnhancement = contrastEnhancement() )
322 {
323 QDomElement contrastEnhancementElem = doc.createElement( QStringLiteral( "sld:ContrastEnhancement" ) );
324 lContrastEnhancement->toSld( doc, contrastEnhancementElem );
325
326 // do changes to minValue/maxValues depending on stretching algorithm. This is necessary because
327 // geoserver does a first stretch on min/max, then applies color map rules.
328 // In some combination it is necessary to use real min/max values and in
329 // others the actual edited min/max values
330 switch ( lContrastEnhancement->contrastEnhancementAlgorithm() )
331 {
334 {
335 // with this renderer export have to be check against real min/max values of the raster
337
338 // if minimum range differ from the real minimum => set is in exported SLD vendor option
339 if ( !qgsDoubleNear( lContrastEnhancement->minimumValue(), myRasterBandStats.minimumValue ) )
340 {
341 // look for VendorOption tag to look for that with minValue attribute
342 const QDomNodeList vendorOptions = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
343 for ( int i = 0; i < vendorOptions.size(); ++i )
344 {
345 QDomElement vendorOption = vendorOptions.at( i ).toElement();
346 if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QLatin1String( "minValue" ) )
347 continue;
348
349 // remove old value and add the new one
350 vendorOption.removeChild( vendorOption.firstChild() );
351 vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
352 }
353 }
354 break;
355 }
357 break;
359 break;
361 break;
362 }
363
364 channelElem.appendChild( contrastEnhancementElem );
365 }
366
367 // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
368 // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
369 QList< QPair< QString, QColor > > classes = legendSymbologyItems();
370
371 // add ColorMap tag
372 QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
373 rasterSymbolizerElem.appendChild( colorMapElem );
374
375 // TODO: add clip intervals basing on real min/max without trigger
376 // min/max calculation again that can takes a lot for remote or big images
377 //
378 // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
379 // each ContrastEnhancementAlgorithm need a specific management.
380 // set type of ColorMap ramp [ramp, intervals, values]
381 // basing on interpolation algorithm of the raster shader
382 QList< QPair< QString, QColor > > colorMapping( classes );
383 switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
384 {
387 {
388 const QString lowValue = classes[0].first;
389 QColor lowColor = classes[0].second;
390 lowColor.setAlpha( 0 );
391 const QString highValue = classes[1].first;
392 QColor highColor = classes[1].second;
393 highColor.setAlpha( 0 );
394
395 colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
396 colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
397 break;
398 }
400 {
401 colorMapping[0].first = QStringLiteral( "0" );
402 colorMapping[1].first = QStringLiteral( "255" );
403 break;
404 }
406 break;
408 break;
409 }
410
411 // create tags
412 for ( auto it = colorMapping.constBegin(); it != colorMapping.constEnd() ; ++it )
413 {
414 // set low level color mapping
415 QDomElement lowColorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
416 colorMapElem.appendChild( lowColorMapEntryElem );
417 lowColorMapEntryElem.setAttribute( QStringLiteral( "color" ), it->second.name() );
418 lowColorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), it->first );
419 if ( it->second.alphaF() == 0.0 )
420 {
421 lowColorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( it->second.alpha() ) );
422 }
423 }
424}
425
427{
428 return mLegendSettings.get();
429}
430
432{
433 if ( settings == mLegendSettings.get() )
434 return;
435 mLegendSettings.reset( settings );
436}
@ InternalLayerOpacityHandling
The renderer internally handles the raster layer's opacity, so the default layer level opacity handli...
DataType
Raster data types.
Definition: qgis.h:242
@ 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.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, 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)
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.
int alphaValue(double value, int globalTransparency=255) const
Returns the transparency value 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.
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.
void setGradient(Gradient gradient)
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:4064
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3509
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38