Skip to content

Commit

Permalink
[FEATURE] Add saturation control for raster images
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Mar 26, 2013
1 parent 4d0be59 commit 030960e
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 3 deletions.
23 changes: 22 additions & 1 deletion src/app/qgsrasterlayerproperties.cpp
Expand Up @@ -45,6 +45,7 @@
#include "qgsrastertransparency.h"
#include "qgssinglebandgrayrendererwidget.h"
#include "qgssinglebandpseudocolorrendererwidget.h"
#include "qgshuesaturationfilter.h"

#include <QTableWidgetItem>
#include <QHeaderView>
Expand Down Expand Up @@ -82,6 +83,10 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer* lyr, QgsMapCanv

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

// Connect saturation slider and spin box
connect( sliderSaturation, SIGNAL( valueChanged( int ) ), spinBoxSaturation, SLOT( setValue( int ) ) );
connect( spinBoxSaturation, SIGNAL( valueChanged( int ) ), sliderSaturation, SLOT( setValue( int ) ) );

// enable or disable Build Pyramids button depending on selection in pyramid list
connect( lbxPyramidResolutions, SIGNAL( itemSelectionChanged() ), this, SLOT( toggleBuildPyramidsButton() ) );

Expand Down Expand Up @@ -243,6 +248,15 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer* lyr, QgsMapCanv
mMaximumOversamplingSpinBox->setValue( resampleFilter->maxOversampling() );
}

// Hue and saturation color control
mHueSaturationGroupBox->setSaveCheckedState( true );
const QgsHueSaturationFilter* hueSaturationFilter = mRasterLayer->hueSaturationFilter();
//set hue and saturation controls to current values
if ( hueSaturationFilter )
{
sliderSaturation->setValue( hueSaturationFilter->saturation() );
}

//blend mode
mBlendModeComboBox->setBlendMode( mRasterLayer->blendMode() );

Expand Down Expand Up @@ -801,6 +815,14 @@ void QgsRasterLayerProperties::apply()
resampleFilter->setMaxOversampling( mMaximumOversamplingSpinBox->value() );
}

// Hue and saturation controls
QgsHueSaturationFilter* hueSaturationFilter = mRasterLayer->hueSaturationFilter();
if ( hueSaturationFilter )
{
hueSaturationFilter->setSaturation( sliderSaturation->value() );
}


//set the blend mode for the layer
mRasterLayer->setBlendMode(( QgsMapLayer::BlendMode ) mBlendModeComboBox->blendMode() );

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


QLinearGradient QgsRasterLayerProperties::redGradient()
{
//define a gradient
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -205,6 +205,7 @@ SET(QGIS_CORE_SRCS
raster/qgssinglebandgrayrenderer.cpp
raster/qgssinglebandpseudocolorrenderer.cpp
raster/qgsbrightnesscontrastfilter.cpp
raster/qgshuesaturationfilter.cpp

renderer/qgscontinuouscolorrenderer.cpp
renderer/qgsgraduatedsymbolrenderer.cpp
Expand Down
206 changes: 206 additions & 0 deletions src/core/raster/qgshuesaturationfilter.cpp
@@ -0,0 +1,206 @@
/***************************************************************************
qgshuesaturationfilter.cpp
---------------------
begin : February 2013
copyright : (C) 2013 by Alexander Bruy, Nyall Dawson
email : alexander dot bruy at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsrasterdataprovider.h"
#include "qgshuesaturationfilter.h"

#include <QDomDocument>
#include <QDomElement>


QgsHueSaturationFilter::QgsHueSaturationFilter( QgsRasterInterface* input )
: QgsRasterInterface( input ),
mSaturation( 0 )
{
}

QgsHueSaturationFilter::~QgsHueSaturationFilter()
{
}

QgsRasterInterface * QgsHueSaturationFilter::clone() const
{
QgsDebugMsg( "Entered hue/saturation filter" );
QgsHueSaturationFilter * filter = new QgsHueSaturationFilter( 0 );
filter->setSaturation( mSaturation );
return filter;
}

int QgsHueSaturationFilter::bandCount() const
{
if ( mOn )
{
return 1;
}

if ( mInput )
{
return mInput->bandCount();
}

return 0;
}

QGis::DataType QgsHueSaturationFilter::dataType( int bandNo ) const
{
if ( mOn )
{
return QGis::ARGB32_Premultiplied;
}

if ( mInput )
{
return mInput->dataType( bandNo );
}

return QGis::UnknownDataType;
}

bool QgsHueSaturationFilter::setInput( QgsRasterInterface* input )
{
QgsDebugMsg( "Entered" );

// Hue/saturation filter can only work with single band ARGB32_Premultiplied
if ( !input )
{
QgsDebugMsg( "No input" );
return false;
}

if ( !mOn )
{
// In off mode we can connect to anything
QgsDebugMsg( "OK" );
mInput = input;
return true;
}

if ( input->bandCount() < 1 )
{
QgsDebugMsg( "No input band" );
return false;
}

if ( input->dataType( 1 ) != QGis::ARGB32_Premultiplied &&
input->dataType( 1 ) != QGis::ARGB32 )
{
QgsDebugMsg( "Unknown input data type" );
return false;
}

mInput = input;
QgsDebugMsg( "OK" );
return true;
}

QgsRasterBlock * QgsHueSaturationFilter::block( int bandNo, QgsRectangle const & extent, int width, int height )
{
Q_UNUSED( bandNo );
QgsDebugMsg( "Entered hue/saturation filter block" );

QgsRasterBlock *outputBlock = new QgsRasterBlock();
if ( !mInput )
{
return outputBlock;
}

// At this moment we know that we read rendered image
int bandNumber = 1;
QgsRasterBlock *inputBlock = mInput->block( bandNumber, extent, width, height );
if ( !inputBlock || inputBlock->isEmpty() )
{
QgsDebugMsg( "No raster data!" );
delete inputBlock;
return outputBlock;
}

if ( mSaturation == 0 )
{
QgsDebugMsg( "No saturation change." );
delete outputBlock;
return inputBlock;
}

if ( !outputBlock->reset( QGis::ARGB32_Premultiplied, width, height ) )
{
delete inputBlock;
return outputBlock;
}

// adjust image
QRgb myNoDataColor = qRgba( 0, 0, 0, 0 );
QColor myColor;
int h, s, v;

// Scale saturation value to [0-2], where 0 = desaturated
double saturationScale = (( double ) mSaturation / 100 ) + 1;

for ( size_t i = 0; i < ( size_t )width*height; i++ )
{
if ( inputBlock->color( i ) == myNoDataColor )
{
outputBlock->setColor( i, myNoDataColor );
continue;
}

// Get current color in hsv
myColor = QColor( inputBlock->color( i ) );
myColor.getHsv( &h, &s, &v );

if ( saturationScale < 1 )
{
// Lowering the saturation. Use a simple linear relationship
s = qMin(( int )( s * saturationScale ), 255 );
}
else
{
// Raising the saturation. Use a saturation curve to prevent
// clipping at maximum saturation with ugly results.
s = qMin(( int )( 255. * ( 1 - pow( 1 - (( double )s / 255. ) , saturationScale * 2 ) ) ), 255 );
}

// Convert back to rgb
myColor = QColor::fromHsv( h, s, v );
outputBlock->setColor( i, myColor.rgb() );
}

delete inputBlock;
return outputBlock;
}

void QgsHueSaturationFilter::writeXML( QDomDocument& doc, QDomElement& parentElem )
{
if ( parentElem.isNull() )
{
return;
}

QDomElement filterElem = doc.createElement( "huesaturation" );

filterElem.setAttribute( "saturation", QString::number( mSaturation ) );
parentElem.appendChild( filterElem );
}

void QgsHueSaturationFilter::readXML( const QDomElement& filterElem )
{
if ( filterElem.isNull() )
{
return;
}

mSaturation = filterElem.attribute( "saturation", "0" ).toInt();
}
59 changes: 59 additions & 0 deletions src/core/raster/qgshuesaturationfilter.h
@@ -0,0 +1,59 @@
/***************************************************************************
qgshuesaturationfilter.h
-------------------
begin : February 2013
copyright : (C) 2013 by Alexander Bruy, Nyall Dawson
email : alexander dot bruy at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSHUESATURATIONFILTER_H
#define QGSHUESATURATIONFILTER_H

#include "qgsrasterdataprovider.h"
#include "qgsrasterinterface.h"

class QDomElement;

/** \ingroup core
* Color and saturation filter pipe for rasters.
*/
class CORE_EXPORT QgsHueSaturationFilter : public QgsRasterInterface
{
public:
QgsHueSaturationFilter( QgsRasterInterface *input = 0 );
~QgsHueSaturationFilter();

QgsRasterInterface * clone() const;

int bandCount() const;

QGis::DataType dataType( int bandNo ) const;

bool setInput( QgsRasterInterface* input );

QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height );

void setSaturation( int saturation ) { mSaturation = qBound( -100, saturation, 100 ); }
int saturation() const { return mSaturation; }

void writeXML( QDomDocument& doc, QDomElement& parentElem );

/**Sets base class members from xml. Usually called from create() methods of subclasses*/
void readXML( const QDomElement& filterElem );

private:
/**Current saturation value. Range: -100 (desaturated) ... 0 (no change) ... 100 (increased)*/
int mSaturation;

};

#endif // QGSHUESATURATIONFILTER_H
22 changes: 22 additions & 0 deletions src/core/raster/qgsrasterlayer.cpp
Expand Up @@ -1702,6 +1702,10 @@ void QgsRasterLayer::setDataProvider( QString const & provider )
QgsBrightnessContrastFilter * brightnessFilter = new QgsBrightnessContrastFilter();
mPipe.set( brightnessFilter );

// hue/saturation filter
QgsHueSaturationFilter * hueSaturationFilter = new QgsHueSaturationFilter();
mPipe.set( hueSaturationFilter );

//resampler (must be after renderer)
QgsRasterResampleFilter * resampleFilter = new QgsRasterResampleFilter();
mPipe.set( resampleFilter );
Expand Down Expand Up @@ -2301,6 +2305,17 @@ bool QgsRasterLayer::readSymbology( const QDomNode& layer_node, QString& errorMe
brightnessFilter->readXML( brightnessElem );
}

//hue/saturation
QgsHueSaturationFilter * hueSaturationFilter = new QgsHueSaturationFilter();
mPipe.set( hueSaturationFilter );

//saturation coefficient
QDomElement hueSaturationElem = layer_node.firstChildElement( "huesaturation" );
if ( !hueSaturationElem.isNull() )
{
hueSaturationFilter->readXML( hueSaturationElem );
}

//resampler
QgsRasterResampleFilter * resampleFilter = new QgsRasterResampleFilter();
mPipe.set( resampleFilter );
Expand Down Expand Up @@ -2500,6 +2515,13 @@ bool QgsRasterLayer::writeSymbology( QDomNode & layer_node, QDomDocument & docum
brightnessFilter->writeXML( document, layerElem );
}

QgsHueSaturationFilter *hueSaturationFilter = mPipe.hueSaturationFilter();
if ( hueSaturationFilter )
{
QDomElement layerElem = layer_node.toElement();
hueSaturationFilter->writeXML( document, layerElem );
}

QgsRasterResampleFilter *resampleFilter = mPipe.resampleFilter();
if ( resampleFilter )
{
Expand Down
2 changes: 2 additions & 0 deletions src/core/raster/qgsrasterlayer.h
Expand Up @@ -44,6 +44,7 @@
#include "qgsrasterinterface.h"
#include "qgsrasterresamplefilter.h"
#include "qgsbrightnesscontrastfilter.h"
#include "qgshuesaturationfilter.h"
#include "qgsrasterdataprovider.h"
#include "qgsrasterpipe.h"

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

QgsBrightnessContrastFilter * brightnessFilter() const { return mPipe.brightnessFilter(); }
QgsHueSaturationFilter * hueSaturationFilter() const { return mPipe.hueSaturationFilter(); }

/** Get raster pipe */
QgsRasterPipe * pipe() { return &mPipe; }
Expand Down

0 comments on commit 030960e

Please sign in to comment.