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