296 changes: 296 additions & 0 deletions src/core/raster/qgshuesaturationfilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
/***************************************************************************
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 ),
mGrayscaleMode( QgsHueSaturationFilter::GrayscaleOff ),
mColorizeOn( false ),
mColorizeColor( QColor::fromRgb( 255, 128, 128 ) ),
mColorizeStrength( 100 )
{
}

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 && mGrayscaleMode == GrayscaleOff && !mColorizeOn )
{
QgsDebugMsg( "No hue/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, l;
int r, g, b;

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

// Get hue, saturation for colorized color
int colorizeH, colorizeS;
colorizeH = mColorizeColor.hue();
colorizeS = mColorizeColor.saturation();

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

// Get hsv and rgb for color
myColor = QColor( inputBlock->color( i ) );
myColor.getHsl( &h, &s, &l );
myColor.getRgb( &r, &g, &b );

switch ( mGrayscaleMode )
{
case GrayscaleLightness:
{
// Lightness mode, set saturation to zero
s = 0;
myColor = QColor::fromHsl( h, s, l );
break;
}
case GrayscaleLuminosity:
{
// Grayscale by weighted rgb components
int luminosity = 0.21 * r + 0.72 * g + 0.07 * b;
r = g = b = luminosity;
myColor = QColor::fromRgb( r, g, b );
break;
}
case GrayscaleAverage:
{
// Grayscale by average of rgb components
int average = ( r + g + b ) / 3;
r = g = b = average;
myColor = QColor::fromRgb( r, g, b );
break;
}
case GrayscaleOff:
{
// Not being made grayscale, do saturation change
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 );
}
myColor = QColor::fromHsl( h, s, l );
break;
}
}

// Colorizing?
if ( mColorizeOn )
{
// Update hsl, rgb values (these may have changed with saturation/grayscale adjustments)
myColor.getHsl( &h, &s, &l );
myColor.getRgb( &r, &g, &b );

// Overwrite hue and saturation with values from colorize color
h = colorizeH;
s = colorizeS;
QColor colorizedColor = QColor::fromHsl( h, s, l );

if ( mColorizeStrength == 100 )
{
// Full strength
myColor = colorizedColor;
}
else
{
// Get rgb for colorized color
int colorizedR, colorizedG, colorizedB;
colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );

// Now, linearly scale by colorize strength
double p = ( double ) mColorizeStrength / 100.;
r = p * colorizedR + ( 1 - p ) * r;
g = p * colorizedG + ( 1 - p ) * g;
b = p * colorizedB + ( 1 - p ) * b;
myColor = QColor::fromRgb( r, g, b );
}
}

// Convert back to rgb
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 ) );
filterElem.setAttribute( "grayscaleMode", QString::number( mGrayscaleMode ) );
filterElem.setAttribute( "colorizeOn", QString::number( mColorizeOn ) );
filterElem.setAttribute( "colorizeRed", QString::number( mColorizeColor.red() ) );
filterElem.setAttribute( "colorizeGreen", QString::number( mColorizeColor.green() ) );
filterElem.setAttribute( "colorizeBlue", QString::number( mColorizeColor.blue() ) );
filterElem.setAttribute( "colorizeStrength", QString::number( mColorizeStrength ) );

parentElem.appendChild( filterElem );
}

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

mSaturation = filterElem.attribute( "saturation", "0" ).toInt();
mGrayscaleMode = ( QgsHueSaturationFilter::GrayscaleMode )filterElem.attribute( "grayscaleMode", "0" ).toInt();

mColorizeOn = ( bool )filterElem.attribute( "colorizeOn", "0" ).toInt();
int mColorizeRed = filterElem.attribute( "colorizeRed", "255" ).toInt();
int mColorizeGreen = filterElem.attribute( "colorizeGreen", "0" ).toInt();
int mColorizeBlue = filterElem.attribute( "colorizeBlue", "0" ).toInt();
mColorizeColor = QColor::fromRgb( mColorizeRed, mColorizeGreen, mColorizeBlue );
mColorizeStrength = filterElem.attribute( "colorizeStrength", "100" ).toInt();

}
87 changes: 87 additions & 0 deletions src/core/raster/qgshuesaturationfilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/***************************************************************************
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:

// Available modes for converting a raster to grayscale
enum GrayscaleMode
{
GrayscaleOff,
GrayscaleLightness,
GrayscaleLuminosity,
GrayscaleAverage
};

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 setGrayscaleMode( QgsHueSaturationFilter::GrayscaleMode grayscaleMode ) { mGrayscaleMode = grayscaleMode; }
QgsHueSaturationFilter::GrayscaleMode grayscaleMode() const { return mGrayscaleMode; }

void setColorizeOn( bool colorizeOn ) { mColorizeOn = colorizeOn; }
bool colorizeOn() const { return mColorizeOn; }
void setColorizeColor( QColor colorizeColor ) { mColorizeColor = colorizeColor; }
QColor colorizeColor() const { return mColorizeColor; }
void setColorizeStrength( int colorizeStrength ) { mColorizeStrength = colorizeStrength; }
int colorizeStrength() const { return mColorizeStrength; }

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;

/**Current grayscale mode*/
QgsHueSaturationFilter::GrayscaleMode mGrayscaleMode;

/**Colorize settings*/
bool mColorizeOn;
QColor mColorizeColor;
int mColorizeStrength;

};

#endif // QGSHUESATURATIONFILTER_H
22 changes: 22 additions & 0 deletions src/core/raster/qgsrasterlayer.cpp
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
15 changes: 13 additions & 2 deletions src/core/raster/qgsrasterpipe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ QgsRasterPipe::Role QgsRasterPipe::interfaceRole( QgsRasterInterface * interface
else if ( dynamic_cast<QgsRasterRenderer *>( interface ) ) role = RendererRole;
else if ( dynamic_cast<QgsRasterResampleFilter *>( interface ) ) role = ResamplerRole;
else if ( dynamic_cast<QgsBrightnessContrastFilter *>( interface ) ) role = BrightnessRole;
else if ( dynamic_cast<QgsHueSaturationFilter *>( interface ) ) role = HueSaturationRole;
else if ( dynamic_cast<QgsRasterProjector *>( interface ) ) role = ProjectorRole;
else if ( dynamic_cast<QgsRasterNuller *>( interface ) ) role = NullerRole;

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

if ( role == ProviderRole )
{
Expand All @@ -193,13 +195,17 @@ bool QgsRasterPipe::set( QgsRasterInterface* theInterface )
{
idx = qMax( providerIdx, rendererIdx ) + 1;
}
else if ( role == ResamplerRole )
else if ( role == HueSaturationRole )
{
idx = qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ) + 1;
}
else if ( role == ResamplerRole )
{
idx = qMax( qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ), hueSaturationIdx ) + 1;
}
else if ( role == ProjectorRole )
{
idx = qMax( qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ), resamplerIdx ) + 1;
idx = qMax( qMax( qMax( qMax( providerIdx, rendererIdx ), brightnessIdx ), hueSaturationIdx ), resamplerIdx ) + 1;
}

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

QgsHueSaturationFilter * QgsRasterPipe::hueSaturationFilter() const
{
return dynamic_cast<QgsHueSaturationFilter *>( interface( HueSaturationRole ) );
}

QgsRasterProjector * QgsRasterPipe::projector() const
{
return dynamic_cast<QgsRasterProjector*>( interface( ProjectorRole ) );
Expand Down
3 changes: 3 additions & 0 deletions src/core/raster/qgsrasterpipe.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "qgsrasterinterface.h"
#include "qgsrasterresamplefilter.h"
#include "qgsbrightnesscontrastfilter.h"
#include "qgshuesaturationfilter.h"
#include "qgsrasterdataprovider.h"
#include "qgsrasternuller.h"
#include "qgsrasterrenderer.h"
Expand All @@ -50,6 +51,7 @@ class CORE_EXPORT QgsRasterPipe
ResamplerRole = 4,
ProjectorRole = 5,
NullerRole = 6,
HueSaturationRole = 7
};

QgsRasterPipe();
Expand Down Expand Up @@ -95,6 +97,7 @@ class CORE_EXPORT QgsRasterPipe
QgsRasterRenderer * renderer() const;
QgsRasterResampleFilter * resampleFilter() const;
QgsBrightnessContrastFilter * brightnessFilter() const;
QgsHueSaturationFilter * hueSaturationFilter() const;
QgsRasterProjector * projector() const;
QgsRasterNuller * nuller() const;

Expand Down
167 changes: 165 additions & 2 deletions src/ui/qgsrasterlayerpropertiesbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
<item row="0" column="0" colspan="4">
<widget class="QTabWidget" name="tabBar">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<property name="iconSize">
<size>
Expand Down Expand Up @@ -345,6 +345,169 @@
</layout>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBox" name="mHueSaturationGroupBox">
<property name="title">
<string>Saturation and hue</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="collapsed" stdset="0">
<bool>false</bool>
</property>
<property name="saveCollapsedState" stdset="0">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="_7">
<item row="0" column="1">
<widget class="QSlider" name="sliderSaturation">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>100</number>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QSpinBox" name="spinBoxSaturation">
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="decimals" stdset="0">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelSaturation">
<property name="text">
<string>Saturation</string>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QLabel" name="labelGrayscale">
<property name="text">
<string>Grayscale</string>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QComboBox" name="comboGrayscale">
<item>
<property name="text">
<string>Off</string>
</property>
</item>
<item>
<property name="text">
<string>By lightness</string>
</property>
</item>
<item>
<property name="text">
<string>By luminosity</string>
</property>
</item>
<item>
<property name="text">
<string>By average</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="mColorizeCheck">
<property name="text">
<string>Colorize</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QgsColorButton" name="btnColorizeColor">
<property name="maximumSize">
<size>
<width>64</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelColorizeStrength">
<property name="text">
<string>Strength</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="sliderColorizeStrength">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>25</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="4">
<widget class="QSpinBox" name="spinColorizeStrength">
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
Expand Down Expand Up @@ -1280,7 +1443,7 @@ p, li { white-space: pre-wrap; }
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Cantarell'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
Expand Down