QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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->setInvertColors( mInvertColors );
36  filter->setSaturation( mSaturation );
37  filter->setGrayscaleMode( mGrayscaleMode );
38  filter->setColorizeOn( mColorizeOn );
39  filter->setColorizeColor( mColorizeColor );
40  filter->setColorizeStrength( mColorizeStrength );
41  return filter;
42 }
43 
45 {
46  if ( mOn )
47  {
48  return 1;
49  }
50 
51  if ( mInput )
52  {
53  return mInput->bandCount();
54  }
55 
56  return 0;
57 }
58 
60 {
61  if ( mOn )
62  {
64  }
65 
66  if ( mInput )
67  {
68  return mInput->dataType( bandNo );
69  }
70 
72 }
73 
75 {
76  QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
77 
78  // Hue/saturation filter can only work with single band ARGB32_Premultiplied
79  if ( !input )
80  {
81  QgsDebugMsg( QStringLiteral( "No input" ) );
82  return false;
83  }
84 
85  if ( !mOn )
86  {
87  // In off mode we can connect to anything
88  QgsDebugMsgLevel( QStringLiteral( "OK" ), 4 );
89  mInput = input;
90  return true;
91  }
92 
93  if ( input->bandCount() < 1 )
94  {
95  QgsDebugMsg( QStringLiteral( "No input band" ) );
96  return false;
97  }
98 
101  {
102  QgsDebugMsg( QStringLiteral( "Unknown input data type" ) );
103  return false;
104  }
105 
106  mInput = input;
107  QgsDebugMsgLevel( QStringLiteral( "OK" ), 4 );
108  return true;
109 }
110 
111 QgsRasterBlock *QgsHueSaturationFilter::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
112 {
113  Q_UNUSED( bandNo )
114  QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2 extent = %3" ).arg( width ).arg( height ).arg( extent.toString() ), 4 );
115 
116  std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
117  if ( !mInput )
118  {
119  return outputBlock.release();
120  }
121 
122  // At this moment we know that we read rendered image
123  int bandNumber = 1;
124  std::unique_ptr< QgsRasterBlock > inputBlock( mInput->block( bandNumber, extent, width, height, feedback ) );
125  if ( !inputBlock || inputBlock->isEmpty() )
126  {
127  QgsDebugMsg( QStringLiteral( "No raster data!" ) );
128  return outputBlock.release();
129  }
130 
131  if ( !mInvertColors && mSaturation == 0 && mGrayscaleMode == GrayscaleOff && !mColorizeOn )
132  {
133  QgsDebugMsgLevel( QStringLiteral( "No hue/saturation change." ), 4 );
134  return inputBlock.release();
135  }
136 
137  if ( !outputBlock->reset( Qgis::DataType::ARGB32_Premultiplied, width, height ) )
138  {
139  return outputBlock.release();
140  }
141 
142  // adjust image
143  QRgb myNoDataColor = qRgba( 0, 0, 0, 0 );
144  QRgb myRgb;
145  QColor myColor;
146  int h, s, l;
147  int r, g, b, alpha;
148  double alphaFactor = 1.0;
149 
150  for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
151  {
152  if ( inputBlock->color( i ) == myNoDataColor )
153  {
154  outputBlock->setColor( i, myNoDataColor );
155  continue;
156  }
157 
158  myRgb = inputBlock->color( i );
159  myColor = QColor( myRgb );
160 
161  // Alpha must be taken from QRgb, since conversion from QRgb->QColor loses alpha
162  alpha = qAlpha( myRgb );
163 
164  if ( alpha == 0 )
165  {
166  // totally transparent, no changes required
167  outputBlock->setColor( i, myRgb );
168  continue;
169  }
170 
171  // Get rgb for color
172  myColor.getRgb( &r, &g, &b );
173 
174  if ( mInvertColors || alpha != 255 )
175  {
176  if ( mInvertColors )
177  {
178  r = 255 - r;
179  g = 255 - g;
180  b = 255 - b;
181  }
182  if ( alpha != 255 )
183  {
184  // Semi-transparent pixel. We need to adjust the colors since we are using Qgis::DataType::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  }
191  myColor = QColor::fromRgb( r, g, b );
192  }
193 
194  myColor.getHsl( &h, &s, &l );
195 
196  // Changing saturation?
197  if ( ( mGrayscaleMode != GrayscaleOff ) || ( mSaturationScale != 1 ) )
198  {
199  processSaturation( r, g, b, h, s, l );
200  }
201 
202  // Colorizing?
203  if ( mColorizeOn )
204  {
205  processColorization( r, g, b, h, s, l );
206  }
207 
208  // Convert back to rgb
209  if ( alpha != 255 )
210  {
211  // Transparent pixel, need to premultiply color components
212  r *= alphaFactor;
213  g *= alphaFactor;
214  b *= alphaFactor;
215  }
216 
217  outputBlock->setColor( i, qRgba( r, g, b, alpha ) );
218  }
219 
220  return outputBlock.release();
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 = std::min( ( 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 = std::min( ( int )( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::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 = std::clamp( saturation, -100, 100 );
328 
329  // Scale saturation value to [0-2], where 0 = desaturated
330  mSaturationScale = ( ( double ) mSaturation / 100 ) + 1;
331 }
332 
333 void QgsHueSaturationFilter::setColorizeColor( const QColor &colorizeColor )
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( "invertColors" ), QString::number( mInvertColors ) );
354  filterElem.setAttribute( QStringLiteral( "colorizeOn" ), QString::number( mColorizeOn ) );
355  filterElem.setAttribute( QStringLiteral( "colorizeRed" ), QString::number( mColorizeColor.red() ) );
356  filterElem.setAttribute( QStringLiteral( "colorizeGreen" ), QString::number( mColorizeColor.green() ) );
357  filterElem.setAttribute( QStringLiteral( "colorizeBlue" ), QString::number( mColorizeColor.blue() ) );
358  filterElem.setAttribute( QStringLiteral( "colorizeStrength" ), QString::number( mColorizeStrength ) );
359 
360  parentElem.appendChild( filterElem );
361 }
362 
363 void QgsHueSaturationFilter::readXml( const QDomElement &filterElem )
364 {
365  if ( filterElem.isNull() )
366  {
367  return;
368  }
369 
370  setSaturation( filterElem.attribute( QStringLiteral( "saturation" ), QStringLiteral( "0" ) ).toInt() );
371  mGrayscaleMode = static_cast< QgsHueSaturationFilter::GrayscaleMode >( filterElem.attribute( QStringLiteral( "grayscaleMode" ), QStringLiteral( "0" ) ).toInt() );
372  mInvertColors = static_cast< bool >( filterElem.attribute( QStringLiteral( "invertColors" ), QStringLiteral( "0" ) ).toInt() );
373 
374  mColorizeOn = static_cast< bool >( filterElem.attribute( QStringLiteral( "colorizeOn" ), QStringLiteral( "0" ) ).toInt() );
375  int mColorizeRed = filterElem.attribute( QStringLiteral( "colorizeRed" ), QStringLiteral( "255" ) ).toInt();
376  int mColorizeGreen = filterElem.attribute( QStringLiteral( "colorizeGreen" ), QStringLiteral( "128" ) ).toInt();
377  int mColorizeBlue = filterElem.attribute( QStringLiteral( "colorizeBlue" ), QStringLiteral( "128" ) ).toInt();
378  setColorizeColor( QColor::fromRgb( mColorizeRed, mColorizeGreen, mColorizeBlue ) );
379  mColorizeStrength = filterElem.attribute( QStringLiteral( "colorizeStrength" ), QStringLiteral( "100" ) ).toInt();
380 
381 }
DataType
Raster data types.
Definition: qgis.h:121
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
@ UnknownDataType
Unknown or unspecified type.
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Color and saturation filter pipe for rasters.
void setColorizeOn(bool colorizeOn)
void setSaturation(int saturation)
void setGrayscaleMode(QgsHueSaturationFilter::GrayscaleMode grayscaleMode)
QgsHueSaturationFilter(QgsRasterInterface *input=nullptr)
Qgis::DataType dataType(int bandNo) const override
Returns data type for the band specified by number.
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
QgsHueSaturationFilter * clone() const override
Clone itself, create deep copy.
void setInvertColors(bool invertColors)
Sets whether the filter will invert colors.
void setColorizeColor(const QColor &colorizeColor)
void setColorizeStrength(int colorizeStrength)
int bandCount() const override
Gets number of bands.
void readXml(const QDomElement &filterElem) override
Sets base class members from xml. Usually called from create() methods of subclasses.
void writeXml(QDomDocument &doc, QDomElement &parentElem) const override
Write base class members to xml.
bool setInput(QgsRasterInterface *input) override
Set input.
Feedback object tailored for raster block reading.
Raster data container.
Base class for processing filters like renderers, reprojector, resampler etc.
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.
virtual Qgis::DataType dataType(int bandNo) const =0
Returns data type for the band specified by number.
virtual QgsRasterInterface * input() const
Current input.
virtual int bandCount() const =0
Gets number of bands.
QgsRasterInterface * mInput
virtual QgsRectangle extent() const
Gets the extent of the interface.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
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:2036
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38