QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
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 QgsDebugError( 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 QgsDebugError( QStringLiteral( "No input band" ) );
96 return false;
97 }
98
101 {
102 QgsDebugError( QStringLiteral( "Unknown input data type" ) );
103 return false;
104 }
105
106 mInput = input;
107 QgsDebugMsgLevel( QStringLiteral( "OK" ), 4 );
108 return true;
109}
110
111QgsRasterBlock *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 QgsDebugError( 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 ( alpha != 255 )
177 {
178 // Semi-transparent pixel. We need to adjust the colors since we are using Qgis::DataType::ARGB32_Premultiplied
179 // and color values have been premultiplied by alpha
180 alphaFactor = alpha / 255.;
181 r /= alphaFactor;
182 g /= alphaFactor;
183 b /= alphaFactor;
184 }
185 if ( mInvertColors )
186 {
187 r = 255 - r;
188 g = 255 - g;
189 b = 255 - b;
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
224void QgsHueSaturationFilter::processColorization( int &r, int &g, int &b, int &h, int &s, int &l ) const
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
262void 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 {
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 }
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
333void 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
342void 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
363void 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:269
@ 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 int bandCount() const =0
Gets number of bands.
QgsRasterInterface * mInput
virtual QgsRectangle extent() const
Gets the extent of the interface.
virtual QgsRasterInterface * input() const
Current input.
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:5747
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38