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