Skip to content

Commit 030960e

Browse files
committedMar 26, 2013
[FEATURE] Add saturation control for raster images
1 parent 4d0be59 commit 030960e

9 files changed

+387
-3
lines changed
 

‎src/app/qgsrasterlayerproperties.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "qgsrastertransparency.h"
4646
#include "qgssinglebandgrayrendererwidget.h"
4747
#include "qgssinglebandpseudocolorrendererwidget.h"
48+
#include "qgshuesaturationfilter.h"
4849

4950
#include <QTableWidgetItem>
5051
#include <QHeaderView>
@@ -82,6 +83,10 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer* lyr, QgsMapCanv
8283

8384
connect( sliderTransparency, SIGNAL( valueChanged( int ) ), this, SLOT( sliderTransparency_valueChanged( int ) ) );
8485

86+
// Connect saturation slider and spin box
87+
connect( sliderSaturation, SIGNAL( valueChanged( int ) ), spinBoxSaturation, SLOT( setValue( int ) ) );
88+
connect( spinBoxSaturation, SIGNAL( valueChanged( int ) ), sliderSaturation, SLOT( setValue( int ) ) );
89+
8590
// enable or disable Build Pyramids button depending on selection in pyramid list
8691
connect( lbxPyramidResolutions, SIGNAL( itemSelectionChanged() ), this, SLOT( toggleBuildPyramidsButton() ) );
8792

@@ -243,6 +248,15 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer* lyr, QgsMapCanv
243248
mMaximumOversamplingSpinBox->setValue( resampleFilter->maxOversampling() );
244249
}
245250

251+
// Hue and saturation color control
252+
mHueSaturationGroupBox->setSaveCheckedState( true );
253+
const QgsHueSaturationFilter* hueSaturationFilter = mRasterLayer->hueSaturationFilter();
254+
//set hue and saturation controls to current values
255+
if ( hueSaturationFilter )
256+
{
257+
sliderSaturation->setValue( hueSaturationFilter->saturation() );
258+
}
259+
246260
//blend mode
247261
mBlendModeComboBox->setBlendMode( mRasterLayer->blendMode() );
248262

@@ -801,6 +815,14 @@ void QgsRasterLayerProperties::apply()
801815
resampleFilter->setMaxOversampling( mMaximumOversamplingSpinBox->value() );
802816
}
803817

818+
// Hue and saturation controls
819+
QgsHueSaturationFilter* hueSaturationFilter = mRasterLayer->hueSaturationFilter();
820+
if ( hueSaturationFilter )
821+
{
822+
hueSaturationFilter->setSaturation( sliderSaturation->value() );
823+
}
824+
825+
804826
//set the blend mode for the layer
805827
mRasterLayer->setBlendMode(( QgsMapLayer::BlendMode ) mBlendModeComboBox->blendMode() );
806828

@@ -1434,7 +1456,6 @@ void QgsRasterLayerProperties::sliderTransparency_valueChanged( int theValue )
14341456
lblTransparencyPercent->setText( QString::number( myInt ) + "%" );
14351457
}//sliderTransparency_valueChanged
14361458

1437-
14381459
QLinearGradient QgsRasterLayerProperties::redGradient()
14391460
{
14401461
//define a gradient

‎src/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ SET(QGIS_CORE_SRCS
205205
raster/qgssinglebandgrayrenderer.cpp
206206
raster/qgssinglebandpseudocolorrenderer.cpp
207207
raster/qgsbrightnesscontrastfilter.cpp
208+
raster/qgshuesaturationfilter.cpp
208209

209210
renderer/qgscontinuouscolorrenderer.cpp
210211
renderer/qgsgraduatedsymbolrenderer.cpp
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
25+
QgsHueSaturationFilter::QgsHueSaturationFilter( QgsRasterInterface* input )
26+
: QgsRasterInterface( input ),
27+
mSaturation( 0 )
28+
{
29+
}
30+
31+
QgsHueSaturationFilter::~QgsHueSaturationFilter()
32+
{
33+
}
34+
35+
QgsRasterInterface * QgsHueSaturationFilter::clone() const
36+
{
37+
QgsDebugMsg( "Entered hue/saturation filter" );
38+
QgsHueSaturationFilter * filter = new QgsHueSaturationFilter( 0 );
39+
filter->setSaturation( mSaturation );
40+
return filter;
41+
}
42+
43+
int QgsHueSaturationFilter::bandCount() const
44+
{
45+
if ( mOn )
46+
{
47+
return 1;
48+
}
49+
50+
if ( mInput )
51+
{
52+
return mInput->bandCount();
53+
}
54+
55+
return 0;
56+
}
57+
58+
QGis::DataType QgsHueSaturationFilter::dataType( int bandNo ) const
59+
{
60+
if ( mOn )
61+
{
62+
return QGis::ARGB32_Premultiplied;
63+
}
64+
65+
if ( mInput )
66+
{
67+
return mInput->dataType( bandNo );
68+
}
69+
70+
return QGis::UnknownDataType;
71+
}
72+
73+
bool QgsHueSaturationFilter::setInput( QgsRasterInterface* input )
74+
{
75+
QgsDebugMsg( "Entered" );
76+
77+
// Hue/saturation filter can only work with single band ARGB32_Premultiplied
78+
if ( !input )
79+
{
80+
QgsDebugMsg( "No input" );
81+
return false;
82+
}
83+
84+
if ( !mOn )
85+
{
86+
// In off mode we can connect to anything
87+
QgsDebugMsg( "OK" );
88+
mInput = input;
89+
return true;
90+
}
91+
92+
if ( input->bandCount() < 1 )
93+
{
94+
QgsDebugMsg( "No input band" );
95+
return false;
96+
}
97+
98+
if ( input->dataType( 1 ) != QGis::ARGB32_Premultiplied &&
99+
input->dataType( 1 ) != QGis::ARGB32 )
100+
{
101+
QgsDebugMsg( "Unknown input data type" );
102+
return false;
103+
}
104+
105+
mInput = input;
106+
QgsDebugMsg( "OK" );
107+
return true;
108+
}
109+
110+
QgsRasterBlock * QgsHueSaturationFilter::block( int bandNo, QgsRectangle const & extent, int width, int height )
111+
{
112+
Q_UNUSED( bandNo );
113+
QgsDebugMsg( "Entered hue/saturation filter block" );
114+
115+
QgsRasterBlock *outputBlock = new QgsRasterBlock();
116+
if ( !mInput )
117+
{
118+
return outputBlock;
119+
}
120+
121+
// At this moment we know that we read rendered image
122+
int bandNumber = 1;
123+
QgsRasterBlock *inputBlock = mInput->block( bandNumber, extent, width, height );
124+
if ( !inputBlock || inputBlock->isEmpty() )
125+
{
126+
QgsDebugMsg( "No raster data!" );
127+
delete inputBlock;
128+
return outputBlock;
129+
}
130+
131+
if ( mSaturation == 0 )
132+
{
133+
QgsDebugMsg( "No saturation change." );
134+
delete outputBlock;
135+
return inputBlock;
136+
}
137+
138+
if ( !outputBlock->reset( QGis::ARGB32_Premultiplied, width, height ) )
139+
{
140+
delete inputBlock;
141+
return outputBlock;
142+
}
143+
144+
// adjust image
145+
QRgb myNoDataColor = qRgba( 0, 0, 0, 0 );
146+
QColor myColor;
147+
int h, s, v;
148+
149+
// Scale saturation value to [0-2], where 0 = desaturated
150+
double saturationScale = (( double ) mSaturation / 100 ) + 1;
151+
152+
for ( size_t i = 0; i < ( size_t )width*height; i++ )
153+
{
154+
if ( inputBlock->color( i ) == myNoDataColor )
155+
{
156+
outputBlock->setColor( i, myNoDataColor );
157+
continue;
158+
}
159+
160+
// Get current color in hsv
161+
myColor = QColor( inputBlock->color( i ) );
162+
myColor.getHsv( &h, &s, &v );
163+
164+
if ( saturationScale < 1 )
165+
{
166+
// Lowering the saturation. Use a simple linear relationship
167+
s = qMin(( int )( s * saturationScale ), 255 );
168+
}
169+
else
170+
{
171+
// Raising the saturation. Use a saturation curve to prevent
172+
// clipping at maximum saturation with ugly results.
173+
s = qMin(( int )( 255. * ( 1 - pow( 1 - (( double )s / 255. ) , saturationScale * 2 ) ) ), 255 );
174+
}
175+
176+
// Convert back to rgb
177+
myColor = QColor::fromHsv( h, s, v );
178+
outputBlock->setColor( i, myColor.rgb() );
179+
}
180+
181+
delete inputBlock;
182+
return outputBlock;
183+
}
184+
185+
void QgsHueSaturationFilter::writeXML( QDomDocument& doc, QDomElement& parentElem )
186+
{
187+
if ( parentElem.isNull() )
188+
{
189+
return;
190+
}
191+
192+
QDomElement filterElem = doc.createElement( "huesaturation" );
193+
194+
filterElem.setAttribute( "saturation", QString::number( mSaturation ) );
195+
parentElem.appendChild( filterElem );
196+
}
197+
198+
void QgsHueSaturationFilter::readXML( const QDomElement& filterElem )
199+
{
200+
if ( filterElem.isNull() )
201+
{
202+
return;
203+
}
204+
205+
mSaturation = filterElem.attribute( "saturation", "0" ).toInt();
206+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/***************************************************************************
2+
qgshuesaturationfilter.h
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+
#ifndef QGSHUESATURATIONFILTER_H
19+
#define QGSHUESATURATIONFILTER_H
20+
21+
#include "qgsrasterdataprovider.h"
22+
#include "qgsrasterinterface.h"
23+
24+
class QDomElement;
25+
26+
/** \ingroup core
27+
* Color and saturation filter pipe for rasters.
28+
*/
29+
class CORE_EXPORT QgsHueSaturationFilter : public QgsRasterInterface
30+
{
31+
public:
32+
QgsHueSaturationFilter( QgsRasterInterface *input = 0 );
33+
~QgsHueSaturationFilter();
34+
35+
QgsRasterInterface * clone() const;
36+
37+
int bandCount() const;
38+
39+
QGis::DataType dataType( int bandNo ) const;
40+
41+
bool setInput( QgsRasterInterface* input );
42+
43+
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height );
44+
45+
void setSaturation( int saturation ) { mSaturation = qBound( -100, saturation, 100 ); }
46+
int saturation() const { return mSaturation; }
47+
48+
void writeXML( QDomDocument& doc, QDomElement& parentElem );
49+
50+
/**Sets base class members from xml. Usually called from create() methods of subclasses*/
51+
void readXML( const QDomElement& filterElem );
52+
53+
private:
54+
/**Current saturation value. Range: -100 (desaturated) ... 0 (no change) ... 100 (increased)*/
55+
int mSaturation;
56+
57+
};
58+
59+
#endif // QGSHUESATURATIONFILTER_H

‎src/core/raster/qgsrasterlayer.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,10 @@ void QgsRasterLayer::setDataProvider( QString const & provider )
17021702
QgsBrightnessContrastFilter * brightnessFilter = new QgsBrightnessContrastFilter();
17031703
mPipe.set( brightnessFilter );
17041704

1705+
// hue/saturation filter
1706+
QgsHueSaturationFilter * hueSaturationFilter = new QgsHueSaturationFilter();
1707+
mPipe.set( hueSaturationFilter );
1708+
17051709
//resampler (must be after renderer)
17061710
QgsRasterResampleFilter * resampleFilter = new QgsRasterResampleFilter();
17071711
mPipe.set( resampleFilter );
@@ -2301,6 +2305,17 @@ bool QgsRasterLayer::readSymbology( const QDomNode& layer_node, QString& errorMe
23012305
brightnessFilter->readXML( brightnessElem );
23022306
}
23032307

2308+
//hue/saturation
2309+
QgsHueSaturationFilter * hueSaturationFilter = new QgsHueSaturationFilter();
2310+
mPipe.set( hueSaturationFilter );
2311+
2312+
//saturation coefficient
2313+
QDomElement hueSaturationElem = layer_node.firstChildElement( "huesaturation" );
2314+
if ( !hueSaturationElem.isNull() )
2315+
{
2316+
hueSaturationFilter->readXML( hueSaturationElem );
2317+
}
2318+
23042319
//resampler
23052320
QgsRasterResampleFilter * resampleFilter = new QgsRasterResampleFilter();
23062321
mPipe.set( resampleFilter );
@@ -2500,6 +2515,13 @@ bool QgsRasterLayer::writeSymbology( QDomNode & layer_node, QDomDocument & docum
25002515
brightnessFilter->writeXML( document, layerElem );
25012516
}
25022517

2518+
QgsHueSaturationFilter *hueSaturationFilter = mPipe.hueSaturationFilter();
2519+
if ( hueSaturationFilter )
2520+
{
2521+
QDomElement layerElem = layer_node.toElement();
2522+
hueSaturationFilter->writeXML( document, layerElem );
2523+
}
2524+
25032525
QgsRasterResampleFilter *resampleFilter = mPipe.resampleFilter();
25042526
if ( resampleFilter )
25052527
{

‎src/core/raster/qgsrasterlayer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "qgsrasterinterface.h"
4545
#include "qgsrasterresamplefilter.h"
4646
#include "qgsbrightnesscontrastfilter.h"
47+
#include "qgshuesaturationfilter.h"
4748
#include "qgsrasterdataprovider.h"
4849
#include "qgsrasterpipe.h"
4950

@@ -364,6 +365,7 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer
364365
QgsRasterResampleFilter * resampleFilter() const { return mPipe.resampleFilter(); }
365366

366367
QgsBrightnessContrastFilter * brightnessFilter() const { return mPipe.brightnessFilter(); }
368+
QgsHueSaturationFilter * hueSaturationFilter() const { return mPipe.hueSaturationFilter(); }
367369

368370
/** Get raster pipe */
369371
QgsRasterPipe * pipe() { return &mPipe; }

‎src/core/raster/qgsrasterpipe.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ QgsRasterPipe::Role QgsRasterPipe::interfaceRole( QgsRasterInterface * interface
128128
else if ( dynamic_cast<QgsRasterRenderer *>( interface ) ) role = RendererRole;
129129
else if ( dynamic_cast<QgsRasterResampleFilter *>( interface ) ) role = ResamplerRole;
130130
else if ( dynamic_cast<QgsBrightnessContrastFilter *>( interface ) ) role = BrightnessRole;
131+
else if ( dynamic_cast<QgsHueSaturationFilter *>( interface ) ) role = HueSaturationRole;
131132
else if ( dynamic_cast<QgsRasterProjector *>( interface ) ) role = ProjectorRole;
132133
else if ( dynamic_cast<QgsRasterNuller *>( interface ) ) role = NullerRole;
133134

@@ -180,6 +181,7 @@ bool QgsRasterPipe::set( QgsRasterInterface* theInterface )
180181
int rendererIdx = mRoleMap.value( RendererRole, -1 );
181182
int resamplerIdx = mRoleMap.value( ResamplerRole, -1 );
182183
int brightnessIdx = mRoleMap.value( BrightnessRole, -1 );
184+
int hueSaturationIdx = mRoleMap.value( HueSaturationRole, -1 );
183185

184186
if ( role == ProviderRole )
185187
{
@@ -193,13 +195,17 @@ bool QgsRasterPipe::set( QgsRasterInterface* theInterface )
193195
{
194196
idx = qMax( providerIdx, rendererIdx ) + 1;
195197
}
196-
else if ( role == ResamplerRole )
198+
else if ( role == HueSaturationRole )
197199
{
198200
idx = qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ) + 1;
199201
}
202+
else if ( role == ResamplerRole )
203+
{
204+
idx = qMax( qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ), hueSaturationIdx ) + 1;
205+
}
200206
else if ( role == ProjectorRole )
201207
{
202-
idx = qMax( qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ), resamplerIdx ) + 1;
208+
idx = qMax( qMax( qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ), hueSaturationIdx ), resamplerIdx ) + 1;
203209
}
204210

205211
return insert( idx, theInterface ); // insert may still fail and return false
@@ -235,6 +241,11 @@ QgsBrightnessContrastFilter * QgsRasterPipe::brightnessFilter() const
235241
return dynamic_cast<QgsBrightnessContrastFilter *>( interface( BrightnessRole ) );
236242
}
237243

244+
QgsHueSaturationFilter * QgsRasterPipe::hueSaturationFilter() const
245+
{
246+
return dynamic_cast<QgsHueSaturationFilter *>( interface( HueSaturationRole ) );
247+
}
248+
238249
QgsRasterProjector * QgsRasterPipe::projector() const
239250
{
240251
return dynamic_cast<QgsRasterProjector*>( interface( ProjectorRole ) );

‎src/core/raster/qgsrasterpipe.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsrasterinterface.h"
2626
#include "qgsrasterresamplefilter.h"
2727
#include "qgsbrightnesscontrastfilter.h"
28+
#include "qgshuesaturationfilter.h"
2829
#include "qgsrasterdataprovider.h"
2930
#include "qgsrasternuller.h"
3031
#include "qgsrasterrenderer.h"
@@ -50,6 +51,7 @@ class CORE_EXPORT QgsRasterPipe
5051
ResamplerRole = 4,
5152
ProjectorRole = 5,
5253
NullerRole = 6,
54+
HueSaturationRole = 7
5355
};
5456

5557
QgsRasterPipe();
@@ -95,6 +97,7 @@ class CORE_EXPORT QgsRasterPipe
9597
QgsRasterRenderer * renderer() const;
9698
QgsRasterResampleFilter * resampleFilter() const;
9799
QgsBrightnessContrastFilter * brightnessFilter() const;
100+
QgsHueSaturationFilter * hueSaturationFilter() const;
98101
QgsRasterProjector * projector() const;
99102
QgsRasterNuller * nuller() const;
100103

‎src/ui/qgsrasterlayerpropertiesbase.ui

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,65 @@
248248
</layout>
249249
</widget>
250250
</item>
251+
<item>
252+
<widget class="QgsCollapsibleGroupBox" name="mHueSaturationGroupBox">
253+
<property name="title">
254+
<string>Saturation and hue</string>
255+
</property>
256+
<property name="checkable">
257+
<bool>false</bool>
258+
</property>
259+
<property name="collapsed" stdset="0">
260+
<bool>false</bool>
261+
</property>
262+
<property name="saveCollapsedState" stdset="0">
263+
<bool>true</bool>
264+
</property>
265+
<layout class="QGridLayout" name="_7">
266+
<item row="0" column="0">
267+
<widget class="QSlider" name="sliderSaturation">
268+
<property name="sizePolicy">
269+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
270+
<horstretch>0</horstretch>
271+
<verstretch>0</verstretch>
272+
</sizepolicy>
273+
</property>
274+
<property name="minimum">
275+
<number>-100</number>
276+
</property>
277+
<property name="maximum">
278+
<number>100</number>
279+
</property>
280+
<property name="orientation">
281+
<enum>Qt::Horizontal</enum>
282+
</property>
283+
<property name="tickPosition">
284+
<enum>QSlider::TicksBelow</enum>
285+
</property>
286+
<property name="tickInterval">
287+
<number>100</number>
288+
</property>
289+
</widget>
290+
</item>
291+
<item row="0" column="1">
292+
<widget class="QSpinBox" name="spinBoxSaturation">
293+
<property name="minimum">
294+
<number>-100</number>
295+
</property>
296+
<property name="maximum">
297+
<number>100</number>
298+
</property>
299+
<property name="value">
300+
<number>0</number>
301+
</property>
302+
<property name="decimals" stdset="0">
303+
<number>0</number>
304+
</property>
305+
</widget>
306+
</item>
307+
</layout>
308+
</widget>
309+
</item>
251310
<item>
252311
<spacer name="verticalSpacer">
253312
<property name="orientation">

0 commit comments

Comments
 (0)
Please sign in to comment.