QGIS API Documentation  2.99.0-Master (90ae728)
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  QgsRasterBlock *outputBlock = new QgsRasterBlock();
123  if ( !mInput )
124  {
125  return outputBlock;
126  }
127 
128  // At this moment we know that we read rendered image
129  int bandNumber = 1;
130  QgsRasterBlock *inputBlock = mInput->block( bandNumber, extent, width, height, feedback );
131  if ( !inputBlock || inputBlock->isEmpty() )
132  {
133  QgsDebugMsg( "No raster data!" );
134  delete inputBlock;
135  return outputBlock;
136  }
137 
138  if ( mSaturation == 0 && mGrayscaleMode == GrayscaleOff && !mColorizeOn )
139  {
140  QgsDebugMsgLevel( "No hue/saturation change.", 4 );
141  delete outputBlock;
142  return inputBlock;
143  }
144 
145  if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
146  {
147  delete inputBlock;
148  return outputBlock;
149  }
150 
151  // adjust image
152  QRgb myNoDataColor = qRgba( 0, 0, 0, 0 );
153  QRgb myRgb;
154  QColor myColor;
155  int h, s, l;
156  int r, g, b, alpha;
157  double alphaFactor = 1.0;
158 
159  for ( qgssize i = 0; i < ( qgssize )width*height; i++ )
160  {
161  if ( inputBlock->color( i ) == myNoDataColor )
162  {
163  outputBlock->setColor( i, myNoDataColor );
164  continue;
165  }
166 
167  myRgb = inputBlock->color( i );
168  myColor = QColor( myRgb );
169 
170  // Alpha must be taken from QRgb, since conversion from QRgb->QColor loses alpha
171  alpha = qAlpha( myRgb );
172 
173  if ( alpha == 0 )
174  {
175  // totally transparent, no changes required
176  outputBlock->setColor( i, myRgb );
177  continue;
178  }
179 
180  // Get rgb for color
181  myColor.getRgb( &r, &g, &b );
182  if ( alpha != 255 )
183  {
184  // Semi-transparent pixel. We need to adjust the colors since we are using Qgis::ARGB32_Premultiplied
185  // and color values have been premultiplied by alpha
186  alphaFactor = alpha / 255.;
187  r /= alphaFactor;
188  g /= alphaFactor;
189  b /= alphaFactor;
190  myColor = QColor::fromRgb( r, g, b );
191  }
192 
193  myColor.getHsl( &h, &s, &l );
194 
195  // Changing saturation?
196  if (( mGrayscaleMode != GrayscaleOff ) || ( mSaturationScale != 1 ) )
197  {
198  processSaturation( r, g, b, h, s, l );
199  }
200 
201  // Colorizing?
202  if ( mColorizeOn )
203  {
204  processColorization( r, g, b, h, s, l );
205  }
206 
207  // Convert back to rgb
208  if ( alpha != 255 )
209  {
210  // Transparent pixel, need to premultiply color components
211  r *= alphaFactor;
212  g *= alphaFactor;
213  b *= alphaFactor;
214  }
215 
216  outputBlock->setColor( i, qRgba( r, g, b, alpha ) );
217  }
218 
219  delete inputBlock;
220  return outputBlock;
221 }
222 
223 // Process a colorization and update resultant HSL & RGB values
224 void QgsHueSaturationFilter::processColorization( int &r, int &g, int &b, int &h, int &s, int &l )
225 {
226  QColor myColor;
227 
228  // Overwrite hue and saturation with values from colorize color
229  h = mColorizeH;
230  s = mColorizeS;
231 
232 
233  QColor colorizedColor = QColor::fromHsl( h, s, l );
234 
235  if ( mColorizeStrength == 100 )
236  {
237  // Full strength
238  myColor = colorizedColor;
239 
240  // RGB may have changed, update them
241  myColor.getRgb( &r, &g, &b );
242  }
243  else
244  {
245  // Get rgb for colorized color
246  int colorizedR, colorizedG, colorizedB;
247  colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
248 
249  // Now, linearly scale by colorize strength
250  double p = ( double ) mColorizeStrength / 100.;
251  r = p * colorizedR + ( 1 - p ) * r;
252  g = p * colorizedG + ( 1 - p ) * g;
253  b = p * colorizedB + ( 1 - p ) * b;
254 
255  // RGB changed, so update HSL values
256  myColor = QColor::fromRgb( r, g, b );
257  myColor.getHsl( &h, &s, &l );
258  }
259 }
260 
261 // Process a change in saturation and update resultant HSL & RGB values
262 void QgsHueSaturationFilter::processSaturation( int &r, int &g, int &b, int &h, int &s, int &l )
263 {
264 
265  QColor myColor;
266 
267  // Are we converting layer to grayscale?
268  switch ( mGrayscaleMode )
269  {
270  case GrayscaleLightness:
271  {
272  // Lightness mode, set saturation to zero
273  s = 0;
274 
275  // Saturation changed, so update rgb values
276  myColor = QColor::fromHsl( h, s, l );
277  myColor.getRgb( &r, &g, &b );
278  return;
279  }
280  case GrayscaleLuminosity:
281  {
282  // Grayscale by weighted rgb components
283  int luminosity = 0.21 * r + 0.72 * g + 0.07 * b;
284  r = g = b = luminosity;
285 
286  // RGB changed, so update HSL values
287  myColor = QColor::fromRgb( r, g, b );
288  myColor.getHsl( &h, &s, &l );
289  return;
290  }
291  case GrayscaleAverage:
292  {
293  // Grayscale by average of rgb components
294  int average = ( r + g + b ) / 3;
295  r = g = b = average;
296 
297  // RGB changed, so update HSL values
298  myColor = QColor::fromRgb( r, g, b );
299  myColor.getHsl( &h, &s, &l );
300  return;
301  }
302  case GrayscaleOff:
303  {
304  // Not being made grayscale, do saturation change
305  if ( mSaturationScale < 1 )
306  {
307  // Lowering the saturation. Use a simple linear relationship
308  s = qMin(( int )( s * mSaturationScale ), 255 );
309  }
310  else
311  {
312  // Raising the saturation. Use a saturation curve to prevent
313  // clipping at maximum saturation with ugly results.
314  s = qMin(( int )( 255. * ( 1 - pow( 1 - ( s / 255. ), pow( mSaturationScale, 2 ) ) ) ), 255 );
315  }
316 
317  // Saturation changed, so update rgb values
318  myColor = QColor::fromHsl( h, s, l );
319  myColor.getRgb( &r, &g, &b );
320  return;
321  }
322  }
323 }
324 
326 {
327  mSaturation = qBound( -100, saturation, 100 );
328 
329  // Scale saturation value to [0-2], where 0 = desaturated
330  mSaturationScale = (( double ) mSaturation / 100 ) + 1;
331 }
332 
334 {
335  mColorizeColor = colorizeColor;
336 
337  // Get hue, saturation for colorized color
338  mColorizeH = mColorizeColor.hue();
339  mColorizeS = mColorizeColor.saturation();
340 }
341 
342 void QgsHueSaturationFilter::writeXml( QDomDocument& doc, QDomElement& parentElem ) const
343 {
344  if ( parentElem.isNull() )
345  {
346  return;
347  }
348 
349  QDomElement filterElem = doc.createElement( QStringLiteral( "huesaturation" ) );
350 
351  filterElem.setAttribute( QStringLiteral( "saturation" ), QString::number( mSaturation ) );
352  filterElem.setAttribute( QStringLiteral( "grayscaleMode" ), QString::number( mGrayscaleMode ) );
353  filterElem.setAttribute( QStringLiteral( "colorizeOn" ), QString::number( mColorizeOn ) );
354  filterElem.setAttribute( QStringLiteral( "colorizeRed" ), QString::number( mColorizeColor.red() ) );
355  filterElem.setAttribute( QStringLiteral( "colorizeGreen" ), QString::number( mColorizeColor.green() ) );
356  filterElem.setAttribute( QStringLiteral( "colorizeBlue" ), QString::number( mColorizeColor.blue() ) );
357  filterElem.setAttribute( QStringLiteral( "colorizeStrength" ), QString::number( mColorizeStrength ) );
358 
359  parentElem.appendChild( filterElem );
360 }
361 
362 void QgsHueSaturationFilter::readXml( const QDomElement& filterElem )
363 {
364  if ( filterElem.isNull() )
365  {
366  return;
367  }
368 
369  setSaturation( filterElem.attribute( QStringLiteral( "saturation" ), QStringLiteral( "0" ) ).toInt() );
370  mGrayscaleMode = ( QgsHueSaturationFilter::GrayscaleMode )filterElem.attribute( QStringLiteral( "grayscaleMode" ), QStringLiteral( "0" ) ).toInt();
371 
372  mColorizeOn = ( bool )filterElem.attribute( QStringLiteral( "colorizeOn" ), QStringLiteral( "0" ) ).toInt();
373  int mColorizeRed = filterElem.attribute( QStringLiteral( "colorizeRed" ), QStringLiteral( "255" ) ).toInt();
374  int mColorizeGreen = filterElem.attribute( QStringLiteral( "colorizeGreen" ), QStringLiteral( "128" ) ).toInt();
375  int mColorizeBlue = filterElem.attribute( QStringLiteral( "colorizeBlue" ), QStringLiteral( "128" ) ).toInt();
376  setColorizeColor( QColor::fromRgb( mColorizeRed, mColorizeGreen, mColorizeBlue ) );
377  mColorizeStrength = filterElem.attribute( QStringLiteral( "colorizeStrength" ), QStringLiteral( "100" ) ).toInt();
378 
379 }
virtual int bandCount() const =0
Get number of bands.
A rectangle specified with double values.
Definition: qgsrectangle.h:36
virtual QgsRectangle extent() const
Get the extent of the interface.
void setColorizeColor(const QColor &colorizeColor)
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
bool setInput(QgsRasterInterface *input) override
Set input.
virtual QgsRasterInterface * input() const
Current input.
DataType
Raster data types.
Definition: qgis.h:61
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.
bool setColor(int row, int column, QRgb color)
Set color on position.
QString toString(bool automaticPrecision=false) const
returns string representation of form xmin,ymin xmax,ymax
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition: qgis.h:76
Raster data container.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:37
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.
QRgb color(int row, int column) const
Read a single color.
bool reset(Qgis::DataType dataType, int width, int height)
Reset block.
Unknown or unspecified type.
Definition: qgis.h:63
bool isEmpty() const
Returns true if block is empty, i.e.
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:338
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:75
void writeXml(QDomDocument &doc, QDomElement &parentElem) const override
Write base class members to xml.
void setGrayscaleMode(QgsHueSaturationFilter::GrayscaleMode grayscaleMode)
void setSaturation(int saturation)