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