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