QGIS API Documentation  2.99.0-Master (f1c3692)
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.
int alphaValue(double, int globalTransparency=255) const
Returns the transparency value for a single value Pixel.
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:39
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
Get the extent of the interface.
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
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)
Get 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.
static const QRgb NODATA_COLOR
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:93
The RasterBandStats struct is a container for statistics about a single raster band.
Raster data container.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:38
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
Return 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.
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:452
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
Get the source color ramp.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Get 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
Get 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.