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