Branch data Line data Source code
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 : :
18 : : #include "qgssinglebandgrayrenderer.h"
19 : : #include "qgscontrastenhancement.h"
20 : : #include "qgsrastertransparency.h"
21 : : #include "qgscolorramplegendnode.h"
22 : : #include "qgscolorramplegendnodesettings.h"
23 : : #include "qgsreadwritecontext.h"
24 : : #include <QDomDocument>
25 : : #include <QDomElement>
26 : : #include <QImage>
27 : : #include <QColor>
28 : : #include <memory>
29 : :
30 : 0 : QgsSingleBandGrayRenderer::QgsSingleBandGrayRenderer( QgsRasterInterface *input, int grayBand )
31 : 0 : : QgsRasterRenderer( input, QStringLiteral( "singlebandgray" ) )
32 : 0 : , mGrayBand( grayBand )
33 : 0 : , mGradient( BlackToWhite )
34 : 0 : , mContrastEnhancement( nullptr )
35 : 0 : , mLegendSettings( std::make_unique< QgsColorRampLegendNodeSettings >() )
36 : 0 : {
37 : 0 : }
38 : :
39 : 0 : QgsSingleBandGrayRenderer *QgsSingleBandGrayRenderer::clone() const
40 : : {
41 : 0 : QgsSingleBandGrayRenderer *renderer = new QgsSingleBandGrayRenderer( nullptr, mGrayBand );
42 : 0 : renderer->copyCommonProperties( this );
43 : :
44 : 0 : renderer->setGradient( mGradient );
45 : 0 : if ( mContrastEnhancement )
46 : : {
47 : 0 : renderer->setContrastEnhancement( new QgsContrastEnhancement( *mContrastEnhancement ) );
48 : 0 : }
49 : 0 : renderer->setLegendSettings( mLegendSettings ? new QgsColorRampLegendNodeSettings( *mLegendSettings.get() ) : new QgsColorRampLegendNodeSettings() );
50 : 0 : return renderer;
51 : 0 : }
52 : :
53 : 0 : QgsRasterRenderer *QgsSingleBandGrayRenderer::create( const QDomElement &elem, QgsRasterInterface *input )
54 : : {
55 : 0 : if ( elem.isNull() )
56 : : {
57 : 0 : return nullptr;
58 : : }
59 : :
60 : 0 : int grayBand = elem.attribute( QStringLiteral( "grayBand" ), QStringLiteral( "-1" ) ).toInt();
61 : 0 : QgsSingleBandGrayRenderer *r = new QgsSingleBandGrayRenderer( input, grayBand );
62 : 0 : r->readXml( elem );
63 : :
64 : 0 : if ( elem.attribute( QStringLiteral( "gradient" ) ) == QLatin1String( "WhiteToBlack" ) )
65 : : {
66 : 0 : r->setGradient( WhiteToBlack ); // BlackToWhite is default
67 : 0 : }
68 : :
69 : 0 : QDomElement contrastEnhancementElem = elem.firstChildElement( QStringLiteral( "contrastEnhancement" ) );
70 : 0 : if ( !contrastEnhancementElem.isNull() )
71 : : {
72 : 0 : QgsContrastEnhancement *ce = new QgsContrastEnhancement( ( Qgis::DataType )(
73 : 0 : input->dataType( grayBand ) ) );
74 : 0 : ce->readXml( contrastEnhancementElem );
75 : 0 : r->setContrastEnhancement( ce );
76 : 0 : }
77 : :
78 : 0 : std::unique_ptr< QgsColorRampLegendNodeSettings > legendSettings = std::make_unique< QgsColorRampLegendNodeSettings >();
79 : 0 : legendSettings->readXml( elem, QgsReadWriteContext() );
80 : 0 : r->setLegendSettings( legendSettings.release() );
81 : :
82 : 0 : return r;
83 : 0 : }
84 : :
85 : 0 : void QgsSingleBandGrayRenderer::setContrastEnhancement( QgsContrastEnhancement *ce )
86 : : {
87 : 0 : mContrastEnhancement.reset( ce );
88 : 0 : }
89 : :
90 : 0 : QgsRasterBlock *QgsSingleBandGrayRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
91 : : {
92 : : Q_UNUSED( bandNo )
93 : 0 : QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2" ).arg( width ).arg( height ), 4 );
94 : :
95 : 0 : std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
96 : 0 : if ( !mInput )
97 : : {
98 : 0 : return outputBlock.release();
99 : : }
100 : :
101 : 0 : std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mGrayBand, extent, width, height, feedback ) );
102 : 0 : if ( !inputBlock || inputBlock->isEmpty() )
103 : : {
104 : 0 : QgsDebugMsg( QStringLiteral( "No raster data!" ) );
105 : 0 : return outputBlock.release();
106 : : }
107 : :
108 : 0 : std::shared_ptr< QgsRasterBlock > alphaBlock;
109 : :
110 : 0 : if ( mAlphaBand > 0 && mGrayBand != mAlphaBand )
111 : : {
112 : 0 : alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
113 : 0 : if ( !alphaBlock || alphaBlock->isEmpty() )
114 : : {
115 : : // TODO: better to render without alpha
116 : 0 : return outputBlock.release();
117 : : }
118 : 0 : }
119 : 0 : else if ( mAlphaBand > 0 )
120 : : {
121 : 0 : alphaBlock = inputBlock;
122 : 0 : }
123 : :
124 : 0 : if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
125 : : {
126 : 0 : return outputBlock.release();
127 : : }
128 : :
129 : 0 : const QRgb myDefaultColor = renderColorForNodataPixel();
130 : 0 : bool isNoData = false;
131 : 0 : for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
132 : : {
133 : 0 : double grayVal = inputBlock->valueAndNoData( i, isNoData );
134 : :
135 : 0 : if ( isNoData )
136 : : {
137 : 0 : outputBlock->setColor( i, myDefaultColor );
138 : 0 : continue;
139 : : }
140 : :
141 : 0 : double currentAlpha = mOpacity;
142 : 0 : if ( mRasterTransparency )
143 : : {
144 : 0 : currentAlpha = mRasterTransparency->alphaValue( grayVal, mOpacity * 255 ) / 255.0;
145 : 0 : }
146 : 0 : if ( mAlphaBand > 0 )
147 : : {
148 : 0 : currentAlpha *= alphaBlock->value( i ) / 255.0;
149 : 0 : }
150 : :
151 : 0 : if ( mContrastEnhancement )
152 : : {
153 : 0 : if ( !mContrastEnhancement->isValueInDisplayableRange( grayVal ) )
154 : : {
155 : 0 : outputBlock->setColor( i, myDefaultColor );
156 : 0 : continue;
157 : : }
158 : 0 : grayVal = mContrastEnhancement->enhanceContrast( grayVal );
159 : 0 : }
160 : :
161 : 0 : if ( mGradient == WhiteToBlack )
162 : : {
163 : 0 : grayVal = 255 - grayVal;
164 : 0 : }
165 : :
166 : 0 : if ( qgsDoubleNear( currentAlpha, 1.0 ) )
167 : : {
168 : 0 : outputBlock->setColor( i, qRgba( grayVal, grayVal, grayVal, 255 ) );
169 : 0 : }
170 : : else
171 : : {
172 : 0 : outputBlock->setColor( i, qRgba( currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * grayVal, currentAlpha * 255 ) );
173 : : }
174 : 0 : }
175 : :
176 : 0 : return outputBlock.release();
177 : 0 : }
178 : :
179 : 0 : void QgsSingleBandGrayRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
180 : : {
181 : 0 : if ( parentElem.isNull() )
182 : : {
183 : 0 : return;
184 : : }
185 : :
186 : 0 : QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
187 : 0 : _writeXml( doc, rasterRendererElem );
188 : :
189 : 0 : rasterRendererElem.setAttribute( QStringLiteral( "grayBand" ), mGrayBand );
190 : :
191 : 0 : QString gradient;
192 : 0 : if ( mGradient == BlackToWhite )
193 : : {
194 : 0 : gradient = QStringLiteral( "BlackToWhite" );
195 : 0 : }
196 : : else
197 : : {
198 : 0 : gradient = QStringLiteral( "WhiteToBlack" );
199 : : }
200 : 0 : rasterRendererElem.setAttribute( QStringLiteral( "gradient" ), gradient );
201 : :
202 : 0 : if ( mContrastEnhancement )
203 : : {
204 : 0 : QDomElement contrastElem = doc.createElement( QStringLiteral( "contrastEnhancement" ) );
205 : 0 : mContrastEnhancement->writeXml( doc, contrastElem );
206 : 0 : rasterRendererElem.appendChild( contrastElem );
207 : 0 : }
208 : :
209 : 0 : if ( mLegendSettings )
210 : 0 : mLegendSettings->writeXml( doc, rasterRendererElem, QgsReadWriteContext() );
211 : :
212 : 0 : parentElem.appendChild( rasterRendererElem );
213 : 0 : }
214 : :
215 : 0 : QList<QPair<QString, QColor> > QgsSingleBandGrayRenderer::legendSymbologyItems() const
216 : : {
217 : 0 : QList<QPair<QString, QColor> > symbolItems;
218 : 0 : if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
219 : : {
220 : 0 : QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
221 : 0 : QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
222 : 0 : symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->minimumValue() ), minColor ) );
223 : 0 : symbolItems.push_back( qMakePair( QString::number( mContrastEnhancement->maximumValue() ), maxColor ) );
224 : 0 : }
225 : 0 : return symbolItems;
226 : 0 : }
227 : :
228 : 0 : QList<QgsLayerTreeModelLegendNode *> QgsSingleBandGrayRenderer::createLegendNodes( QgsLayerTreeLayer *nodeLayer )
229 : : {
230 : 0 : QList<QgsLayerTreeModelLegendNode *> res;
231 : 0 : if ( mContrastEnhancement && mContrastEnhancement->contrastEnhancementAlgorithm() != QgsContrastEnhancement::NoEnhancement )
232 : : {
233 : 0 : const QString name = displayBandName( mGrayBand );
234 : 0 : if ( !name.isEmpty() )
235 : : {
236 : 0 : res << new QgsSimpleLegendNode( nodeLayer, name );
237 : 0 : }
238 : :
239 : 0 : const QColor minColor = ( mGradient == BlackToWhite ) ? Qt::black : Qt::white;
240 : 0 : const QColor maxColor = ( mGradient == BlackToWhite ) ? Qt::white : Qt::black;
241 : 0 : res << new QgsColorRampLegendNode( nodeLayer, new QgsGradientColorRamp( minColor, maxColor ),
242 : 0 : mLegendSettings ? *mLegendSettings : QgsColorRampLegendNodeSettings(),
243 : 0 : mContrastEnhancement->minimumValue(),
244 : 0 : mContrastEnhancement->maximumValue() );
245 : 0 : }
246 : 0 : return res;
247 : 0 : }
248 : :
249 : 0 : QList<int> QgsSingleBandGrayRenderer::usesBands() const
250 : : {
251 : 0 : QList<int> bandList;
252 : 0 : if ( mGrayBand != -1 )
253 : : {
254 : 0 : bandList << mGrayBand;
255 : 0 : }
256 : 0 : return bandList;
257 : 0 : }
258 : :
259 : 0 : void QgsSingleBandGrayRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
260 : : {
261 : : // create base structure
262 : 0 : QgsRasterRenderer::toSld( doc, element, props );
263 : :
264 : : // look for RasterSymbolizer tag
265 : 0 : QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
266 : 0 : if ( elements.size() == 0 )
267 : 0 : return;
268 : :
269 : : // there SHOULD be only one
270 : 0 : QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
271 : :
272 : : // add Channel Selection tags
273 : : // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
274 : : // after opacity or geometry or as first element after sld:RasterSymbolizer
275 : 0 : QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
276 : 0 : elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
277 : 0 : if ( elements.size() != 0 )
278 : : {
279 : 0 : rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
280 : 0 : }
281 : : else
282 : : {
283 : 0 : elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
284 : 0 : if ( elements.size() != 0 )
285 : : {
286 : 0 : rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
287 : 0 : }
288 : : else
289 : : {
290 : 0 : rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
291 : : }
292 : : }
293 : :
294 : : // for gray band
295 : 0 : QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
296 : 0 : channelSelectionElem.appendChild( channelElem );
297 : :
298 : : // set band
299 : 0 : QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
300 : 0 : sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( grayBand() ) ) );
301 : 0 : channelElem.appendChild( sourceChannelNameElem );
302 : :
303 : : // set ContrastEnhancement
304 : 0 : if ( auto *lContrastEnhancement = contrastEnhancement() )
305 : : {
306 : 0 : QDomElement contrastEnhancementElem = doc.createElement( QStringLiteral( "sld:ContrastEnhancement" ) );
307 : 0 : lContrastEnhancement->toSld( doc, contrastEnhancementElem );
308 : :
309 : : // do changes to minValue/maxValues depending on stretching algorithm. This is necessary because
310 : : // geoserver does a first stretch on min/max, then applies color map rules.
311 : : // In some combination it is necessary to use real min/max values and in
312 : : // others the actual edited min/max values
313 : 0 : switch ( lContrastEnhancement->contrastEnhancementAlgorithm() )
314 : : {
315 : : case QgsContrastEnhancement::StretchAndClipToMinimumMaximum:
316 : : case QgsContrastEnhancement::ClipToMinimumMaximum:
317 : : {
318 : : // with this renderer export have to be check against real min/max values of the raster
319 : 0 : QgsRasterBandStats myRasterBandStats = mInput->bandStatistics( grayBand(), QgsRasterBandStats::Min | QgsRasterBandStats::Max );
320 : :
321 : : // if minimum range differ from the real minimum => set is in exported SLD vendor option
322 : 0 : if ( !qgsDoubleNear( lContrastEnhancement->minimumValue(), myRasterBandStats.minimumValue ) )
323 : : {
324 : : // look for VendorOption tag to look for that with minValue attribute
325 : 0 : const QDomNodeList vendorOptions = contrastEnhancementElem.elementsByTagName( QStringLiteral( "sld:VendorOption" ) );
326 : 0 : for ( int i = 0; i < vendorOptions.size(); ++i )
327 : : {
328 : 0 : QDomElement vendorOption = vendorOptions.at( i ).toElement();
329 : 0 : if ( vendorOption.attribute( QStringLiteral( "name" ) ) != QLatin1String( "minValue" ) )
330 : 0 : continue;
331 : :
332 : : // remove old value and add the new one
333 : 0 : vendorOption.removeChild( vendorOption.firstChild() );
334 : 0 : vendorOption.appendChild( doc.createTextNode( QString::number( myRasterBandStats.minimumValue ) ) );
335 : 0 : }
336 : 0 : }
337 : 0 : break;
338 : : }
339 : : case QgsContrastEnhancement::UserDefinedEnhancement:
340 : 0 : break;
341 : : case QgsContrastEnhancement::NoEnhancement:
342 : 0 : break;
343 : : case QgsContrastEnhancement::StretchToMinimumMaximum:
344 : 0 : break;
345 : : }
346 : :
347 : 0 : channelElem.appendChild( contrastEnhancementElem );
348 : 0 : }
349 : :
350 : : // for each color set a ColorMapEntry tag nested into "sld:ColorMap" tag
351 : : // e.g. <ColorMapEntry color="#EEBE2F" quantity="-300" label="label" opacity="0"/>
352 : 0 : QList< QPair< QString, QColor > > classes = legendSymbologyItems();
353 : :
354 : : // add ColorMap tag
355 : 0 : QDomElement colorMapElem = doc.createElement( QStringLiteral( "sld:ColorMap" ) );
356 : 0 : rasterSymbolizerElem.appendChild( colorMapElem );
357 : :
358 : : // TODO: add clip intervals basing on real min/max without trigger
359 : : // min/max calculation again that can takes a lot for remote or big images
360 : : //
361 : : // contrast enhancement against a color map can be SLD simulated playing with ColorMapEntryies
362 : : // each ContrastEnhancementAlgorithm need a specific management.
363 : : // set type of ColorMap ramp [ramp, intervals, values]
364 : : // basing on interpolation algorithm of the raster shader
365 : 0 : QList< QPair< QString, QColor > > colorMapping( classes );
366 : 0 : switch ( contrastEnhancement()->contrastEnhancementAlgorithm() )
367 : : {
368 : : case ( QgsContrastEnhancement::StretchAndClipToMinimumMaximum ):
369 : : case ( QgsContrastEnhancement::ClipToMinimumMaximum ):
370 : : {
371 : 0 : QString lowValue = classes[0].first;
372 : 0 : QColor lowColor = classes[0].second;
373 : 0 : lowColor.setAlpha( 0 );
374 : 0 : QString highValue = classes[1].first;
375 : 0 : QColor highColor = classes[1].second;
376 : 0 : highColor.setAlpha( 0 );
377 : :
378 : 0 : colorMapping.prepend( QPair< QString, QColor >( lowValue, lowColor ) );
379 : 0 : colorMapping.append( QPair< QString, QColor >( highValue, highColor ) );
380 : : break;
381 : 0 : }
382 : : case ( QgsContrastEnhancement::StretchToMinimumMaximum ):
383 : : {
384 : 0 : colorMapping[0].first = QStringLiteral( "0" );
385 : 0 : colorMapping[1].first = QStringLiteral( "255" );
386 : 0 : break;
387 : : }
388 : : case ( QgsContrastEnhancement::UserDefinedEnhancement ):
389 : 0 : break;
390 : : case ( QgsContrastEnhancement::NoEnhancement ):
391 : 0 : break;
392 : : }
393 : :
394 : : // create tags
395 : 0 : for ( auto it = colorMapping.constBegin(); it != colorMapping.constEnd() ; ++it )
396 : : {
397 : : // set low level color mapping
398 : 0 : QDomElement lowColorMapEntryElem = doc.createElement( QStringLiteral( "sld:ColorMapEntry" ) );
399 : 0 : colorMapElem.appendChild( lowColorMapEntryElem );
400 : 0 : lowColorMapEntryElem.setAttribute( QStringLiteral( "color" ), it->second.name() );
401 : 0 : lowColorMapEntryElem.setAttribute( QStringLiteral( "quantity" ), it->first );
402 : 0 : if ( it->second.alphaF() == 0.0 )
403 : : {
404 : 0 : lowColorMapEntryElem.setAttribute( QStringLiteral( "opacity" ), QString::number( it->second.alpha() ) );
405 : 0 : }
406 : 0 : }
407 : 0 : }
408 : :
409 : 0 : const QgsColorRampLegendNodeSettings *QgsSingleBandGrayRenderer::legendSettings() const
410 : : {
411 : 0 : return mLegendSettings.get();
412 : : }
413 : :
414 : 0 : void QgsSingleBandGrayRenderer::setLegendSettings( QgsColorRampLegendNodeSettings *settings )
415 : : {
416 : 0 : if ( settings == mLegendSettings.get() )
417 : 0 : return;
418 : 0 : mLegendSettings.reset( settings );
419 : 0 : }
|