QGIS API Documentation  3.2.0-Bonn (bc43194)
qgspalettedrasterrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspalettedrasterrenderer.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 #include "qgsrastertransparency.h"
20 #include "qgsrasterviewport.h"
21 #include "qgssymbollayerutils.h"
22 
23 #include <QColor>
24 #include <QDomDocument>
25 #include <QDomElement>
26 #include <QImage>
27 #include <QVector>
28 #include <memory>
29 
31  : QgsRasterRenderer( input, QStringLiteral( "paletted" ) )
32  , mBand( bandNumber )
33  , mClassData( classes )
34 {
35  updateArrays();
36 }
37 
39 {
40  QgsPalettedRasterRenderer *renderer = new QgsPalettedRasterRenderer( nullptr, mBand, mClassData );
41  if ( mSourceColorRamp )
42  renderer->setSourceColorRamp( mSourceColorRamp->clone() );
43 
44  renderer->copyCommonProperties( this );
45  return renderer;
46 }
47 
49 {
50  if ( elem.isNull() )
51  {
52  return nullptr;
53  }
54 
55  int bandNumber = elem.attribute( QStringLiteral( "band" ), QStringLiteral( "-1" ) ).toInt();
56  ClassData classData;
57 
58  QDomElement paletteElem = elem.firstChildElement( QStringLiteral( "colorPalette" ) );
59  if ( !paletteElem.isNull() )
60  {
61  QDomNodeList paletteEntries = paletteElem.elementsByTagName( QStringLiteral( "paletteEntry" ) );
62 
63  QDomElement entryElem;
64  int value;
65 
66  for ( int i = 0; i < paletteEntries.size(); ++i )
67  {
68  QColor color;
69  QString label;
70  entryElem = paletteEntries.at( i ).toElement();
71  value = ( int )entryElem.attribute( QStringLiteral( "value" ), QStringLiteral( "0" ) ).toDouble();
72  QgsDebugMsgLevel( entryElem.attribute( "color", "#000000" ), 4 );
73  color = QColor( entryElem.attribute( QStringLiteral( "color" ), QStringLiteral( "#000000" ) ) );
74  color.setAlpha( entryElem.attribute( QStringLiteral( "alpha" ), QStringLiteral( "255" ) ).toInt() );
75  label = entryElem.attribute( QStringLiteral( "label" ) );
76  classData << Class( value, color, label );
77  }
78  }
79 
80  QgsPalettedRasterRenderer *r = new QgsPalettedRasterRenderer( input, bandNumber, classData );
81  r->readXml( elem );
82 
83  // try to load color ramp (optional)
84  QDomElement sourceColorRampElem = elem.firstChildElement( QStringLiteral( "colorramp" ) );
85  if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
86  {
87  r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) );
88  }
89 
90  return r;
91 }
92 
94 {
95  return mClassData;
96 }
97 
98 QString QgsPalettedRasterRenderer::label( int idx ) const
99 {
100  Q_FOREACH ( const Class &c, mClassData )
101  {
102  if ( c.value == idx )
103  return c.label;
104  }
105 
106  return QString();
107 }
108 
109 void QgsPalettedRasterRenderer::setLabel( int idx, const QString &label )
110 {
111  ClassData::iterator cIt = mClassData.begin();
112  for ( ; cIt != mClassData.end(); ++cIt )
113  {
114  if ( cIt->value == idx )
115  {
116  cIt->label = label;
117  return;
118  }
119  }
120 }
121 
122 QgsRasterBlock *QgsPalettedRasterRenderer::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
123 {
124  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
125  if ( !mInput || mClassData.isEmpty() )
126  {
127  return outputBlock.release();
128  }
129 
130  std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( bandNo, extent, width, height, feedback ) );
131 
132  if ( !inputBlock || inputBlock->isEmpty() )
133  {
134  QgsDebugMsg( "No raster data!" );
135  return outputBlock.release();
136  }
137 
138  double currentOpacity = mOpacity;
139 
140  //rendering is faster without considering user-defined transparency
141  bool hasTransparency = usesTransparency();
142 
143  std::shared_ptr< QgsRasterBlock > alphaBlock;
144 
145  if ( mAlphaBand > 0 && mAlphaBand != mBand )
146  {
147  alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
148  if ( !alphaBlock || alphaBlock->isEmpty() )
149  {
150  return outputBlock.release();
151  }
152  }
153  else if ( mAlphaBand == mBand )
154  {
155  alphaBlock = inputBlock;
156  }
157 
158  if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
159  {
160  return outputBlock.release();
161  }
162 
163  QRgb myDefaultColor = NODATA_COLOR;
164 
165  //use direct data access instead of QgsRasterBlock::setValue
166  //because of performance
167  unsigned int *outputData = ( unsigned int * )( outputBlock->bits() );
168 
169  qgssize rasterSize = ( qgssize )width * height;
170  for ( qgssize i = 0; i < rasterSize; ++i )
171  {
172  if ( inputBlock->isNoData( i ) )
173  {
174  outputData[i] = myDefaultColor;
175  continue;
176  }
177  int val = ( int ) inputBlock->value( i );
178  if ( !mColors.contains( val ) )
179  {
180  outputData[i] = myDefaultColor;
181  continue;
182  }
183 
184  if ( !hasTransparency )
185  {
186  outputData[i] = mColors.value( val );
187  }
188  else
189  {
190  currentOpacity = mOpacity;
191  if ( mRasterTransparency )
192  {
193  currentOpacity = mRasterTransparency->alphaValue( val, mOpacity * 255 ) / 255.0;
194  }
195  if ( mAlphaBand > 0 )
196  {
197  currentOpacity *= alphaBlock->value( i ) / 255.0;
198  }
199 
200  QRgb c = mColors.value( val );
201  outputData[i] = qRgba( currentOpacity * qRed( c ), currentOpacity * qGreen( c ), currentOpacity * qBlue( c ), currentOpacity * qAlpha( c ) );
202  }
203  }
204 
205  return outputBlock.release();
206 }
207 
208 void QgsPalettedRasterRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
209 {
210  if ( parentElem.isNull() )
211  {
212  return;
213  }
214 
215  QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
216  _writeXml( doc, rasterRendererElem );
217 
218  rasterRendererElem.setAttribute( QStringLiteral( "band" ), mBand );
219  QDomElement colorPaletteElem = doc.createElement( QStringLiteral( "colorPalette" ) );
220  ClassData::const_iterator it = mClassData.constBegin();
221  for ( ; it != mClassData.constEnd(); ++it )
222  {
223  QColor color = it->color;
224  QDomElement colorElem = doc.createElement( QStringLiteral( "paletteEntry" ) );
225  colorElem.setAttribute( QStringLiteral( "value" ), it->value );
226  colorElem.setAttribute( QStringLiteral( "color" ), color.name() );
227  colorElem.setAttribute( QStringLiteral( "alpha" ), color.alpha() );
228  if ( !it->label.isEmpty() )
229  {
230  colorElem.setAttribute( QStringLiteral( "label" ), it->label );
231  }
232  colorPaletteElem.appendChild( colorElem );
233  }
234  rasterRendererElem.appendChild( colorPaletteElem );
235 
236  // save source color ramp
237  if ( mSourceColorRamp )
238  {
239  QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
240  rasterRendererElem.appendChild( colorRampElem );
241  }
242 
243  parentElem.appendChild( rasterRendererElem );
244 }
245 
246 void QgsPalettedRasterRenderer::legendSymbologyItems( QList< QPair< QString, QColor > > &symbolItems ) const
247 {
248  ClassData::const_iterator it = mClassData.constBegin();
249  for ( ; it != mClassData.constEnd(); ++it )
250  {
251  QString lab = it->label.isEmpty() ? QString::number( it->value ) : it->label;
252  symbolItems << qMakePair( lab, it->color );
253  }
254 }
255 
257 {
258  QList<int> bandList;
259  if ( mBand != -1 )
260  {
261  bandList << mBand;
262  }
263  return bandList;
264 }
265 
267 {
268  mSourceColorRamp.reset( ramp );
269 }
270 
272 {
273  return mSourceColorRamp.get();
274 }
275 
276 QgsPalettedRasterRenderer::ClassData QgsPalettedRasterRenderer::colorTableToClassData( const QList<QgsColorRampShader::ColorRampItem> &table )
277 {
278  QList<QgsColorRampShader::ColorRampItem>::const_iterator colorIt = table.constBegin();
280  for ( ; colorIt != table.constEnd(); ++colorIt )
281  {
282  int idx = ( int )( colorIt->value );
283  classes << QgsPalettedRasterRenderer::Class( idx, colorIt->color, colorIt->label );
284  }
285  return classes;
286 }
287 
289 {
291 
292  QRegularExpression linePartRx( QStringLiteral( "[\\s,:]+" ) );
293 
294  QStringList parts = string.split( '\n', QString::SkipEmptyParts );
295  Q_FOREACH ( const QString &part, parts )
296  {
297  QStringList lineParts = part.split( linePartRx, QString::SkipEmptyParts );
298  bool ok = false;
299  switch ( lineParts.count() )
300  {
301  case 1:
302  {
303  int value = lineParts.at( 0 ).toInt( &ok );
304  if ( !ok )
305  continue;
306 
307  classes << Class( value );
308  break;
309  }
310 
311  case 2:
312  {
313  int value = lineParts.at( 0 ).toInt( &ok );
314  if ( !ok )
315  continue;
316 
317  QColor c( lineParts.at( 1 ) );
318 
319  classes << Class( value, c );
320  break;
321  }
322 
323  default:
324  {
325  if ( lineParts.count() < 4 )
326  continue;
327 
328  int value = lineParts.at( 0 ).toInt( &ok );
329  if ( !ok )
330  continue;
331 
332  bool rOk = false;
333  double r = lineParts.at( 1 ).toDouble( &rOk );
334  bool gOk = false;
335  double g = lineParts.at( 2 ).toDouble( &gOk );
336  bool bOk = false;
337  double b = lineParts.at( 3 ).toDouble( &bOk );
338 
339  QColor c;
340  if ( rOk && gOk && bOk )
341  {
342  c = QColor( r, g, b );
343  }
344 
345  if ( lineParts.count() >= 5 )
346  {
347  double alpha = lineParts.at( 4 ).toDouble( &ok );
348  if ( ok )
349  c.setAlpha( alpha );
350  }
351 
352  QString label;
353  if ( lineParts.count() > 5 )
354  {
355  label = lineParts.mid( 5 ).join( ' ' );
356  }
357 
358  classes << Class( value, c, label );
359  break;
360  }
361  }
362 
363  }
364  return classes;
365 }
366 
368 {
369  QFile inputFile( path );
370  QString input;
371  if ( inputFile.open( QIODevice::ReadOnly ) )
372  {
373  QTextStream in( &inputFile );
374  input = in.readAll();
375  inputFile.close();
376  }
377  return classDataFromString( input );
378 }
379 
381 {
382  QStringList out;
383  // must be sorted
385  std::sort( cd.begin(), cd.end(), []( const Class & a, const Class & b ) -> bool
386  {
387  return a.value < b.value;
388  } );
389 
390  Q_FOREACH ( const Class &c, cd )
391  {
392  out << QStringLiteral( "%1 %2 %3 %4 %5 %6" ).arg( c.value ).arg( c.color.red() )
393  .arg( c.color.green() ).arg( c.color.blue() ).arg( c.color.alpha() ).arg( c.label );
394  }
395  return out.join( '\n' );
396 }
397 
399 {
400  if ( !raster )
401  return ClassData();
402 
403  // get min and max value from raster
404  QgsRasterBandStats stats = raster->bandStatistics( bandNumber, QgsRasterBandStats::Min | QgsRasterBandStats::Max, QgsRectangle(), 0, feedback );
405  if ( feedback && feedback->isCanceled() )
406  return ClassData();
407 
408  double min = stats.minimumValue;
409  double max = stats.maximumValue;
410  // need count of every individual value
411  int bins = std::ceil( max - min ) + 1;
412  if ( bins <= 0 )
413  return ClassData();
414 
415  QgsRasterHistogram histogram = raster->histogram( bandNumber, bins, min, max, QgsRectangle(), 0, false, feedback );
416  if ( feedback && feedback->isCanceled() )
417  return ClassData();
418 
419  double interval = ( histogram.maximum - histogram.minimum + 1 ) / histogram.binCount;
420 
421  ClassData data;
422 
423  double currentValue = histogram.minimum;
424  double presentValues = 0;
425  for ( int idx = 0; idx < histogram.binCount; ++idx )
426  {
427  int count = histogram.histogramVector.at( idx );
428  if ( count > 0 )
429  {
430  data << Class( currentValue, QColor(), QString::number( currentValue ) );
431  presentValues++;
432  }
433  currentValue += interval;
434  }
435 
436  // assign colors from ramp
437  if ( ramp )
438  {
439  int i = 0;
440 
441  if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp ) )
442  {
443  //ramp is a random colors ramp, so inform it of the total number of required colors
444  //this allows the ramp to pregenerate a set of visually distinctive colors
445  randomRamp->setTotalColorCount( data.count() );
446  }
447 
448  if ( presentValues > 1 )
449  presentValues -= 1; //avoid duplicate first color
450 
451  QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
452  for ( ; cIt != data.end(); ++cIt )
453  {
454  cIt->color = ramp->color( i / presentValues );
455  i++;
456  }
457  }
458  return data;
459 }
460 
461 void QgsPalettedRasterRenderer::updateArrays()
462 {
463  mColors.clear();
464  int i = 0;
465  ClassData::const_iterator it = mClassData.constBegin();
466  for ( ; it != mClassData.constEnd(); ++it )
467  {
468  mColors[it->value] = qPremultiply( it->color.rgba() );
469  i++;
470  }
471 }
void setSourceColorRamp(QgsColorRamp *ramp)
Set the source color ramp.
static QString classDataToString(const QgsPalettedRasterRenderer::ClassData &classes)
Converts classes to a string representation, using the .clr/gdal color table file format...
A rectangle specified with double values.
Definition: qgsrectangle.h:40
QgsPalettedRasterRenderer(QgsRasterInterface *input, int bandNumber, const ClassData &classes)
Constructor for QgsPalettedRasterRenderer.
QColor color
Color to render value.
Renderer for paletted raster images.
virtual QgsRectangle extent() const
Gets the extent of the interface.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
double minimum
The minimum histogram value.
virtual QgsRasterInterface * input() const
Current input.
ClassData classes() const
Returns a map of value to classes (colors) used by the renderer.
Properties of a single value class.
static QDomElement saveColorRamp(const QString &name, QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp&#39;s settings to an XML element.
void setLabel(int idx, const QString &label)
Set category label.
double maximumValue
The maximum cell value in the raster band.
void writeXml(QDomDocument &doc, QDomElement &parentElem) const override
Write base class members to xml.
virtual QgsRasterHistogram histogram(int bandNo, int binCount=0, double minimum=std::numeric_limits< double >::quiet_NaN(), double maximum=std::numeric_limits< double >::quiet_NaN(), const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, bool includeOutOfRange=false, QgsRasterBlockFeedback *feedback=nullptr)
Returns a band histogram.
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
QgsRasterTransparency * mRasterTransparency
Raster transparency per color or value. Overwrites global alpha value.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
static const QRgb NODATA_COLOR
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:106
The RasterBandStats struct is a container for statistics about a single raster band.
Raster data container.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
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.
QList< QgsPalettedRasterRenderer::Class > ClassData
Map of value to class properties.
void _writeXml(QDomDocument &doc, QDomElement &rasterRendererElem) const
Write upper class info into rasterrenderer element (called by writeXml method of subclasses) ...
static QgsPalettedRasterRenderer::ClassData classDataFromString(const QString &string)
Converts a string containing a color table or class data to to paletted renderer class data...
void copyCommonProperties(const QgsRasterRenderer *other, bool copyMinMaxOrigin=true)
Copies common properties like opacity / transparency data from other renderer.
bool usesTransparency() const
QgsRasterHistogram::HistogramVector histogramVector
Store the histogram for a given layer.
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
QString label(int idx) const
Returns optional category label.
int mAlphaBand
Read alpha value from band.
void readXml(const QDomElement &rendererElem) override
Sets base class members from xml. Usually called from create() methods of subclasses.
Base class for processing filters like renderers, reprojector, resampler etc.
int alphaValue(double value, int globalTransparency=255) const
Returns the transparency value for a single value pixel.
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:510
static QgsRasterRenderer * create(const QDomElement &elem, QgsRasterInterface *input)
Totally random color ramp.
Definition: qgscolorramp.h:427
QList< int > usesBands() const override
Returns a list of band numbers used by the renderer.
double maximum
The maximum histogram value.
QgsColorRamp * sourceColorRamp() const
Gets the source color ramp.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
QgsPalettedRasterRenderer * clone() const override
Clone itself, create deep copy.
static QgsPalettedRasterRenderer::ClassData classDataFromFile(const QString &path)
Opens a color table file and returns corresponding paletted renderer class data.
static QgsPalettedRasterRenderer::ClassData classDataFromRaster(QgsRasterInterface *raster, int bandNumber, QgsColorRamp *ramp=nullptr, QgsRasterBlockFeedback *feedback=nullptr)
Generates class data from a raster, for the specified bandNumber.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void legendSymbologyItems(QList< QPair< QString, QColor > > &symbolItems) const override
Gets symbology items if provided by renderer.
double minimumValue
The minimum cell value in the raster band.
The QgsRasterHistogram is a container for histogram of a single raster band.
int binCount
Number of bins (intervals,buckets) in histogram.
double mOpacity
Global alpha value (0-1)
QgsRasterInterface * mInput
Feedback object tailored for raster block reading.
static QgsPalettedRasterRenderer::ClassData colorTableToClassData(const QList< QgsColorRampShader::ColorRampItem > &table)
Converts a raster color table to paletted renderer class data.
Raster renderer pipe that applies colors to a raster.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.