Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
gamma correction filter pipe for rasters (fix #13512)
  • Loading branch information
alexbruy committed Jun 30, 2020
1 parent c019737 commit 8054469
Show file tree
Hide file tree
Showing 13 changed files with 423 additions and 4 deletions.
62 changes: 62 additions & 0 deletions python/core/auto_generated/raster/qgsgammacorrectionfilter.sip.in
@@ -0,0 +1,62 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/raster/qgsgammacorrectionfilter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/





class QgsGammaCorrectionFilter : QgsRasterInterface
{
%Docstring
Gamma correction filter pipe for rasters.

.. versionadded:: 3.16
%End

%TypeHeaderCode
#include "qgsgammacorrectionfilter.h"
%End
public:
QgsGammaCorrectionFilter( QgsRasterInterface *input = 0 );

virtual QgsGammaCorrectionFilter *clone() const /Factory/;


virtual int bandCount() const;


virtual Qgis::DataType dataType( int bandNo ) const;


virtual bool setInput( QgsRasterInterface *input );


virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback = 0 ) /Factory/;


void setGamma( double gamma );
double gamma() const;

virtual void writeXml( QDomDocument &doc, QDomElement &parentElem ) const;


virtual void readXml( const QDomElement &filterElem );

%Docstring
Sets base class members from xml. Usually called from :py:func:`~QgsGammaCorrectionFilter.create` methods of subclasses
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/raster/qgsgammacorrectionfilter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
3 changes: 3 additions & 0 deletions python/core/auto_generated/raster/qgsrasterinterface.sip.in
Expand Up @@ -95,6 +95,7 @@ Base class for processing filters like renderers, reprojector, resampler etc.
#include "qgsrasterinterface.h"
// QgsRasterInterface subclasses
#include <qgsbrightnesscontrastfilter.h>
#include <qgsgammacorrectionfilter.h>
#include <qgshuesaturationfilter.h>
#include <qgsrasterdataprovider.h>
#include <qgsrasternuller.h>
Expand All @@ -115,6 +116,8 @@ Base class for processing filters like renderers, reprojector, resampler etc.
sipType = sipType_QgsBrightnessContrastFilter;
else if ( dynamic_cast<QgsHueSaturationFilter *>( sipCpp ) )
sipType = sipType_QgsHueSaturationFilter;
else if ( dynamic_cast<QgsGammaCorrectionFilter *>( sipCpp ) )
sipType = sipType_QgsGammaCorrectionFilter;
else if ( dynamic_cast<QgsRasterDataProvider *>( sipCpp ) )
{
sipType = sipType_QgsRasterDataProvider;
Expand Down
19 changes: 19 additions & 0 deletions python/core/auto_generated/raster/qgsrasterlayer.sip.in
Expand Up @@ -190,6 +190,8 @@ Returns the raster's resample filter.

.. seealso:: :py:func:`brightnessFilter`

.. seealso:: :py:func:`gammaCorrectionFilter`

.. seealso:: :py:func:`hueSaturationFilter`
%End

Expand All @@ -199,7 +201,22 @@ Returns the raster's brightness/contrast filter.

.. seealso:: :py:func:`resampleFilter`

.. seealso:: :py:func:`gammaCorrectionFilter`

.. seealso:: :py:func:`hueSaturationFilter`
%End

QgsGammaCorrectionFilter *gammaCorrectionFilter() const;
%Docstring
Returns the raster's gamma correction filter.

.. seealso:: :py:func:`resampleFilter`

.. seealso:: :py:func:`brightnessFilter`

.. seealso:: :py:func:`hueSaturationFilter`

.. versionadded:: 3.16
%End

QgsHueSaturationFilter *hueSaturationFilter() const;
Expand All @@ -209,6 +226,8 @@ Returns the raster's hue/saturation filter.
.. seealso:: :py:func:`resampleFilter`

.. seealso:: :py:func:`brightnessFilter`

.. seealso:: :py:func:`gammaCorrectionFilter`
%End

void setResamplingStage( QgsRasterPipe::ResamplingStage stage );
Expand Down
10 changes: 9 additions & 1 deletion python/core/auto_generated/raster/qgsrasterpipe.sip.in
Expand Up @@ -30,7 +30,8 @@ Base class for processing modules.
ResamplerRole,
ProjectorRole,
NullerRole,
HueSaturationRole
HueSaturationRole,
GammaCorrectionRole
};

QgsRasterPipe();
Expand Down Expand Up @@ -102,6 +103,13 @@ Test if interface at index may be switched on/off
QgsRasterRenderer *renderer() const;
QgsRasterResampleFilter *resampleFilter() const;
QgsBrightnessContrastFilter *brightnessFilter() const;

QgsGammaCorrectionFilter *gammaCorrectionFilter() const;
%Docstring
Getter for gamma correction filter pipe

.. versionadded:: 3.16
%End
QgsHueSaturationFilter *hueSaturationFilter() const;
QgsRasterProjector *projector() const;
QgsRasterNuller *nuller() const;
Expand Down
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -469,6 +469,7 @@
%Include auto_generated/raster/qgscontrastenhancementfunction.sip
%Include auto_generated/raster/qgscubicrasterresampler.sip
%Include auto_generated/raster/qgshillshaderenderer.sip
%Include auto_generated/raster/qgsgammacorrectionfilter.sip
%Include auto_generated/raster/qgshuesaturationfilter.sip
%Include auto_generated/raster/qgslinearminmaxenhancement.sip
%Include auto_generated/raster/qgslinearminmaxenhancementwithclip.sip
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -572,6 +572,7 @@ SET(QGIS_CORE_SRCS
raster/qgsbilinearrasterresampler.cpp
raster/qgsbrightnesscontrastfilter.cpp
raster/qgscubicrasterresampler.cpp
raster/qgsgammacorrectionfilter.cpp
raster/qgshuesaturationfilter.cpp
raster/qgsmultibandcolorrenderer.cpp
raster/qgspalettedrasterrenderer.cpp
Expand Down Expand Up @@ -1306,6 +1307,7 @@ SET(QGIS_CORE_HDRS
raster/qgscontrastenhancement.h
raster/qgscontrastenhancementfunction.h
raster/qgscubicrasterresampler.h
raster/qgsgammacorrectionfilter.h
raster/qgshillshaderenderer.h
raster/qgshuesaturationfilter.h
raster/qgslinearminmaxenhancement.h
Expand Down
208 changes: 208 additions & 0 deletions src/core/raster/qgsgammacorrectionfilter.cpp
@@ -0,0 +1,208 @@
/***************************************************************************
qgsgammacorrectionfilter.cpp
---------------------
begin : June 2020
copyright : (C) 2020 by Alexander Bruy
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 "qgsgammacorrectionfilter.h"

#include <QDomDocument>
#include <QDomElement>

QgsGammaCorrectionFilter::QgsGammaCorrectionFilter( QgsRasterInterface *input )
: QgsRasterInterface( input )
{
}

QgsGammaCorrectionFilter *QgsGammaCorrectionFilter::clone() const
{
QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
QgsGammaCorrectionFilter *filter = new QgsGammaCorrectionFilter( nullptr );
filter->setGamma( mGamma );
return filter;
}

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

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

return 0;
}

Qgis::DataType QgsGammaCorrectionFilter::dataType( int bandNo ) const
{
if ( mOn )
{
return Qgis::ARGB32_Premultiplied;
}

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

return Qgis::UnknownDataType;
}

bool QgsGammaCorrectionFilter::setInput( QgsRasterInterface *input )
{
QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );

// Gamma correction filter can only work with single band ARGB32_Premultiplied
if ( !input )
{
QgsDebugMsgLevel( QStringLiteral( "No input" ), 4 );
return false;
}

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

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

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

mInput = input;
QgsDebugMsgLevel( QStringLiteral( "OK" ), 4 );
return true;
}

QgsRasterBlock *QgsGammaCorrectionFilter::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
{
Q_UNUSED( bandNo )
QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2 extent = %3" ).arg( width ).arg( height ).arg( extent.toString() ), 4 );

std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
if ( !mInput )
{
return outputBlock.release();
}

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

if ( mGamma == 1.0 )
{
QgsDebugMsgLevel( QStringLiteral( "No gamma changes." ), 4 );
return inputBlock.release();
}

if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
{
return outputBlock.release();
}

// adjust image
QRgb myNoDataColor = qRgba( 0, 0, 0, 0 );
QRgb myColor;

int r, g, b, alpha;
double gammaCorrection = 1.0 / mGamma;

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

myColor = inputBlock->color( i );
alpha = qAlpha( myColor );

r = adjustColorComponent( qRed( myColor ), alpha, gammaCorrection );
g = adjustColorComponent( qGreen( myColor ), alpha, gammaCorrection );
b = adjustColorComponent( qBlue( myColor ), alpha, gammaCorrection );

outputBlock->setColor( i, qRgba( r, g, b, alpha ) );
}

return outputBlock.release();
}

int QgsGammaCorrectionFilter::adjustColorComponent( int colorComponent, int alpha, double gammaCorrection ) const
{
if ( alpha == 255 )
{
// Opaque pixel, do simpler math
return qBound( 0, ( int )( 255 * std::pow( colorComponent / 255.0, gammaCorrection ) ), 255 );
}
else if ( alpha == 0 )
{
// Totally transparent pixel
return 0;
}
else
{
// Semi-transparent pixel. We need to adjust the math since we are using Qgis::ARGB32_Premultiplied
// and color values have been premultiplied by alpha
double alphaFactor = alpha / 255.;
double adjustedColor = colorComponent / alphaFactor;

// Make sure to return a premultiplied color
return alphaFactor * qBound( 0., 255 * std::pow( adjustedColor / 255.0, gammaCorrection ), 255. );
}
}

void QgsGammaCorrectionFilter::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
{
if ( parentElem.isNull() )
{
return;
}

QDomElement filterElem = doc.createElement( QStringLiteral( "gammacorrection" ) );

filterElem.setAttribute( QStringLiteral( "gamma" ), QString::number( mGamma ) );
parentElem.appendChild( filterElem );
}

void QgsGammaCorrectionFilter::readXml( const QDomElement &filterElem )
{
if ( filterElem.isNull() )
{
return;
}

mGamma = filterElem.attribute( QStringLiteral( "gamma" ), QStringLiteral( "1" ) ).toDouble();
}

0 comments on commit 8054469

Please sign in to comment.