QGIS API Documentation  2.99.0-Master (53aba61)
qgshuesaturationfilter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgshuesaturationfilter.cpp
3  ---------------------
4  begin : February 2013
5  copyright : (C) 2013 by Alexander Bruy, Nyall Dawson
6  email : alexander dot bruy at gmail dot com
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 "qgsrasterdataprovider.h"
19 #include "qgshuesaturationfilter.h"
20 
21 #include <QDomDocument>
22 #include <QDomElement>
23 
24 
26  : QgsRasterInterface( input )
27  , mSaturation( 0 )
28  , mSaturationScale( 1 )
29  , mGrayscaleMode( QgsHueSaturationFilter::GrayscaleOff )
30  , mColorizeOn( false )
31  , mColorizeColor( QColor::fromRgb( 255, 128, 128 ) )
32  , mColorizeH( 0 )
33  , mColorizeS( 50 )
34  , mColorizeStrength( 100 )
35 {
36 }
37 
39 {
40  QgsDebugMsgLevel( "Entered hue/saturation filter", 4 );
41  QgsHueSaturationFilter *filter = new QgsHueSaturationFilter( nullptr );
42  filter->setSaturation( mSaturation );
43  filter->setGrayscaleMode( mGrayscaleMode );
44  filter->setColorizeOn( mColorizeOn );
45  filter->setColorizeColor( mColorizeColor );
46  filter->setColorizeStrength( mColorizeStrength );
47  return filter;
48 }
49 
51 {
52  if ( mOn )
53  {
54  return 1;
55  }
56 
57  if ( mInput )
58  {
59  return mInput->bandCount();
60  }
61 
62  return 0;
63 }
64 
66 {
67  if ( mOn )
68  {
70  }
71 
72  if ( mInput )
73  {
74  return mInput->dataType( bandNo );
75  }
76 
77  return Qgis::UnknownDataType;
78 }
79 
81 {
82  QgsDebugMsgLevel( "Entered", 4 );
83 
84  // Hue/saturation filter can only work with single band ARGB32_Premultiplied
85  if ( !input )
86  {
87  QgsDebugMsg( "No input" );
88  return false;
89  }
90 
91  if ( !mOn )
92  {
93  // In off mode we can connect to anything
94  QgsDebugMsgLevel( "OK", 4 );
95  mInput = input;
96  return true;
97  }
98 
99  if ( input->bandCount() < 1 )
100  {
101  QgsDebugMsg( "No input band" );
102  return false;
103  }
104 
105  if ( input->dataType( 1 ) != Qgis::ARGB32_Premultiplied &&
106  input->dataType( 1 ) != Qgis::ARGB32 )
107  {
108  QgsDebugMsg( "Unknown input data type" );
109  return false;
110  }
111 
112  mInput = input;
113  QgsDebugMsgLevel( "OK", 4 );
114  return true;
115 }
116 
117 QgsRasterBlock *QgsHueSaturationFilter::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
118 {
119  Q_UNUSED( bandNo );
120  QgsDebugMsgLevel( QString( "width = %1 height = %2 extent = %3" ).arg( width ).arg( height ).arg( extent.toString() ), 4 );
121 
122  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
123  if ( !mInput )
124  {
125  return outputBlock.release();
126  }
127 
128  // At this moment we know that we read rendered image
129  int bandNumber = 1;
130  std::unique_ptr< QgsRasterBlock > inputBlock( mInput->block( bandNumber, extent, width, height, feedback ) );
131  if ( !inputBlock || inputBlock->isEmpty() )
132  {
133  QgsDebugMsg( "No raster data!" );
134  return outputBlock.release();
135  }
136 
137  if ( mSaturation == 0 && mGrayscaleMode == GrayscaleOff && !mColorizeOn )
138  {
139  QgsDebugMsgLevel( "No hue/saturation change.", 4 );
140  return inputBlock.release();
141  }
142 
143  if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
144  {
145  return outputBlock.release();
146  }
147 
148  // adjust image
149  QRgb myNoDataColor = qRgba( 0, 0, 0, 0 );
150  QRgb myRgb;
151  QColor myColor;
152  int h, s, l;
153  int r, g, b, alpha;
154  double alphaFactor = 1.0;
155 
156  for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
157  {
158  if ( inputBlock->color( i ) == myNoDataColor )
159  {
160  outputBlock->setColor( i, myNoDataColor );
161  continue;
162  }
163 
164  myRgb = inputBlock->color( i );
165  myColor = QColor( myRgb );
166 
167  // Alpha must be taken from QRgb, since conversion from QRgb->QColor loses alpha
168  alpha = qAlpha( myRgb );
169 
170  if ( alpha == 0 )
171  {
172  // totally transparent, no changes required
173  outputBlock->setColor( i, myRgb );
174  continue;
175  }
176 
177  // Get rgb for color
178  myColor.getRgb( &r, &g, &b );
179  if ( alpha != 255 )
180  {
181  // Semi-transparent pixel. We need to adjust the colors since we are using Qgis::ARGB32_Premultiplied
182  // and color values have been premultiplied by alpha
183  alphaFactor = alpha / 255.;
184  r /= alphaFactor;
185  g /= alphaFactor;
186  b /= alphaFactor;
187  myColor = QColor::fromRgb( r, g, b );
188  }
189 
190  myColor.getHsl( &h, &s, &l );
191 
192  // Changing saturation?
193  if ( ( mGrayscaleMode != GrayscaleOff ) || ( mSaturationScale != 1 ) )
194  {
195  processSaturation( r, g, b, h, s, l );
196  }
197 
198  // Colorizing?
199  if ( mColorizeOn )
200  {
201  processColorization( r, g, b, h, s, l );
202  }
203 
204  // Convert back to rgb
205  if ( alpha != 255 )
206  {
207  // Transparent pixel, need to premultiply color components
208  r *= alphaFactor;
209  g *= alphaFactor;
210  b *= alphaFactor;
211  }
212 
213  outputBlock->setColor( i, qRgba( r, g, b, alpha ) );
214  }
215 
216  return outputBlock.release();
217 }
218 
219 // Process a colorization and update resultant HSL & RGB values
220 void QgsHueSaturationFilter::processColorization( int &r, int &g, int &b, int &h, int &s, int &l )
221 {
222  QColor myColor;
223 
224  // Overwrite hue and saturation with values from colorize color
225  h = mColorizeH;
226  s = mColorizeS;
227 
228 
229  QColor colorizedColor = QColor::fromHsl( h, s, l );
230 
231  if ( mColorizeStrength == 100 )
232  {
233  // Full strength
234  myColor = colorizedColor;
235 
236  // RGB may have changed, update them
237  myColor.getRgb( &r, &g, &b );
238  }
239  else
240  {
241  // Get rgb for colorized color
242  int colorizedR, colorizedG, colorizedB;
243  colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
244 
245  // Now, linearly scale by colorize strength
246  double p = ( double ) mColorizeStrength / 100.;
247  r = p * colorizedR + ( 1 - p ) * r;
248  g = p * colorizedG + ( 1 - p ) * g;
249  b = p * colorizedB + ( 1 - p ) * b;
250 
251  // RGB changed, so update HSL values
252  myColor = QColor::fromRgb( r, g, b );
253  myColor.getHsl( &h, &s, &l );
254  }
255 }
256 
257 // Process a change in saturation and update resultant HSL & RGB values
258 void QgsHueSaturationFilter::processSaturation( int &r, int &g, int &b, int &h, int &s, int &l )
259 {
260 
261  QColor myColor;
262 
263  // Are we converting layer to grayscale?
264  switch ( mGrayscaleMode )
265  {
266  case GrayscaleLightness:
267  {
268  // Lightness mode, set saturation to zero
269  s = 0;
270 
271  // Saturation changed, so update rgb values
272  myColor = QColor::fromHsl( h, s, l );
273  myColor.getRgb( &r, &g, &b );
274  return;
275  }
276  case GrayscaleLuminosity:
277  {
278  // Grayscale by weighted rgb components
279  int luminosity = 0.21 * r + 0.72 * g + 0.07 * b;
280  r = g = b = luminosity;
281 
282  // RGB changed, so update HSL values
283  myColor = QColor::fromRgb( r, g, b );
284  myColor.getHsl( &h, &s, &l );
285  return;
286  }
287  case GrayscaleAverage:
288  {
289  // Grayscale by average of rgb components
290  int average = ( r + g + b ) / 3;
291  r = g = b = average;
292 
293  // RGB changed, so update HSL values
294  myColor = QColor::fromRgb( r, g, b );
295  myColor.getHsl( &h, &s, &l );
296  return;
297  }
298  case GrayscaleOff:
299  {
300  // Not being made grayscale, do saturation change
301  if ( mSaturationScale < 1 )
302  {
303  // Lowering the saturation. Use a simple linear relationship
304  s = std::min( ( int )( s * mSaturationScale ), 255 );
305  }
306  else
307  {
308  // Raising the saturation. Use a saturation curve to prevent
309  // clipping at maximum saturation with ugly results.
310  s = std::min( ( int )( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::pow( mSaturationScale, 2 ) ) ) ), 255 );
311  }
312 
313  // Saturation changed, so update rgb values
314  myColor = QColor::fromHsl( h, s, l );
315  myColor.getRgb( &r, &g, &b );
316  return;
317  }
318  }
319 }
320 
322 {
323  mSaturation = qBound( -100, saturation, 100 );
324 
325  // Scale saturation value to [0-2], where 0 = desaturated
326  mSaturationScale = ( ( double ) mSaturation / 100 ) + 1;
327 }
328 
330 {
331  mColorizeColor = colorizeColor;
332 
333  // Get hue, saturation for colorized color
334  mColorizeH = mColorizeColor.hue();
335  mColorizeS = mColorizeColor.saturation();
336 }
337 
338 void QgsHueSaturationFilter::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
339 {
340  if ( parentElem.isNull() )
341  {
342  return;
343  }
344 
345  QDomElement filterElem = doc.createElement( QStringLiteral( "huesaturation" ) );
346 
347  filterElem.setAttribute( QStringLiteral( "saturation" ), QString::number( mSaturation ) );
348  filterElem.setAttribute( QStringLiteral( "grayscaleMode" ), QString::number( mGrayscaleMode ) );
349  filterElem.setAttribute( QStringLiteral( "colorizeOn" ), QString::number( mColorizeOn ) );
350  filterElem.setAttribute( QStringLiteral( "colorizeRed" ), QString::number( mColorizeColor.red() ) );
351  filterElem.setAttribute( QStringLiteral( "colorizeGreen" ), QString::number( mColorizeColor.green() ) );
352  filterElem.setAttribute( QStringLiteral( "colorizeBlue" ), QString::number( mColorizeColor.blue() ) );
353  filterElem.setAttribute( QStringLiteral( "colorizeStrength" ), QString::number( mColorizeStrength ) );
354 
355  parentElem.appendChild( filterElem );
356 }
357 
358 void QgsHueSaturationFilter::readXml( const QDomElement &filterElem )
359 {
360  if ( filterElem.isNull() )
361  {
362  return;
363  }
364 
365  setSaturation( filterElem.attribute( QStringLiteral( "saturation" ), QStringLiteral( "0" ) ).toInt() );
366  mGrayscaleMode = ( QgsHueSaturationFilter::GrayscaleMode )filterElem.attribute( QStringLiteral( "grayscaleMode" ), QStringLiteral( "0" ) ).toInt();
367 
368  mColorizeOn = ( bool )filterElem.attribute( QStringLiteral( "colorizeOn" ), QStringLiteral( "0" ) ).toInt();
369  int mColorizeRed = filterElem.attribute( QStringLiteral( "colorizeRed" ), QStringLiteral( "255" ) ).toInt();
370  int mColorizeGreen = filterElem.attribute( QStringLiteral( "colorizeGreen" ), QStringLiteral( "128" ) ).toInt();
371  int mColorizeBlue = filterElem.attribute( QStringLiteral( "colorizeBlue" ), QStringLiteral( "128" ) ).toInt();
372  setColorizeColor( QColor::fromRgb( mColorizeRed, mColorizeGreen, mColorizeBlue ) );
373  mColorizeStrength = filterElem.attribute( QStringLiteral( "colorizeStrength" ), QStringLiteral( "100" ) ).toInt();
374 
375 }
virtual int bandCount() const =0
Get number of bands.
A rectangle specified with double values.
Definition: qgsrectangle.h:38
virtual QgsRectangle extent() const
Get the extent of the interface.
void setColorizeColor(const QColor &colorizeColor)
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
bool setInput(QgsRasterInterface *input) override
Set input.
virtual QgsRasterInterface * input() const
Current input.
DataType
Raster data types.
Definition: qgis.h:74
int bandCount() const override
Get number of bands.
virtual Qgis::DataType dataType(int bandNo) const =0
Returns data type for the band specified by number.
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:89
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.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
Unknown or unspecified type.
Definition: qgis.h:76
void setColorizeOn(bool colorizeOn)
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:374
QgsHueSaturationFilter(QgsRasterInterface *input=nullptr)
Color and saturation filter pipe for rasters.
QgsHueSaturationFilter * clone() const override
Clone itself, create deep copy.
void readXml(const QDomElement &filterElem) override
Sets base class members from xml. Usually called from create() methods of subclasses.
void setColorizeStrength(int colorizeStrength)
QgsRasterInterface * mInput
Feedback object tailored for raster block reading.
Qgis::DataType dataType(int bandNo) const override
Returns data type for the band specified by number.
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition: qgis.h:88
void writeXml(QDomDocument &doc, QDomElement &parentElem) const override
Write base class members to xml.
void setGrayscaleMode(QgsHueSaturationFilter::GrayscaleMode grayscaleMode)
void setSaturation(int saturation)