Skip to content
Permalink
Browse files

Merge pull request #37456 from alexbruy/gamma-correction

Gamma correction filter for raster layers
  • Loading branch information
alexbruy committed Jul 1, 2020
2 parents 059f9fa + 6a6f7ff commit 6068c64ce516162833f729623495f5d7ebf836fe
Showing with 833 additions and 371 deletions.
  1. +2 −0 images/images.qrc
  2. +1 −0 images/themes/default/mActionDecreaseGamma.svg
  3. +1 −0 images/themes/default/mActionIncreaseGamma.svg
  4. +68 −1 python/core/auto_generated/raster/qgsbrightnesscontrastfilter.sip.in
  5. +1 −1 python/core/auto_generated/raster/qgsrasterpipe.sip.in
  6. +52 −0 src/app/qgisapp.cpp
  7. +23 −1 src/app/qgisapp.h
  8. +12 −8 src/core/raster/qgsbrightnesscontrastfilter.cpp
  9. +60 −4 src/core/raster/qgsbrightnesscontrastfilter.h
  10. +1 −1 src/core/raster/qgsrasterpipe.h
  11. +19 −3 src/gui/raster/qgsrasterlayerproperties.cpp
  12. +10 −0 src/gui/raster/qgsrasterlayerproperties.h
  13. +17 −0 src/gui/raster/qgsrendererrasterpropertieswidget.cpp
  14. +12 −0 src/gui/raster/qgsrendererrasterpropertieswidget.h
  15. +27 −1 src/ui/qgisapp.ui
  16. +146 −78 src/ui/qgsrasterlayerpropertiesbase.ui
  17. +274 −255 src/ui/qgsrendererrasterpropswidgetbase.ui
  18. +1 −1 tests/code_layout/acceptable_missing_doc.py
  19. +106 −17 tests/src/python/test_qgsrasterlayer.py
  20. BIN tests/testdata/control_images/expected_raster_brightness20/expected_raster_brightness20.png
  21. BIN tests/testdata/control_images/expected_raster_brightness50/expected_raster_brightness50.png
  22. BIN tests/testdata/control_images/expected_raster_contrast100/expected_raster_contrast100.png
  23. BIN tests/testdata/control_images/expected_raster_contrast30/expected_raster_contrast30.png
  24. BIN tests/testdata/control_images/expected_raster_gamma022/expected_raster_gamma022.png
  25. BIN tests/testdata/control_images/expected_raster_gamma222/expected_raster_gamma222.png
@@ -274,6 +274,7 @@
<file>themes/default/mActionCustomProjection.svg</file>
<file>themes/default/mActionDecreaseBrightness.svg</file>
<file>themes/default/mActionDecreaseContrast.svg</file>
<file>themes/default/mActionDecreaseGamma.svg</file>
<file>themes/default/mActionDeleteAttribute.svg</file>
<file>themes/default/mActionDeletePart.svg</file>
<file>themes/default/mActionDeleteRing.svg</file>
@@ -318,6 +319,7 @@
<file>themes/default/mActionIdentify.svg</file>
<file>themes/default/mActionIncreaseBrightness.svg</file>
<file>themes/default/mActionIncreaseContrast.svg</file>
<file>themes/default/mActionIncreaseGamma.svg</file>
<file>themes/default/mActionInOverview.svg</file>
<file>themes/default/mActionInvertSelection.svg</file>
<file>themes/default/mActionKeyboardShortcuts.svg</file>
@@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m15.29989 4.213459q0 1.4192708-1.731771 4.752604-1.302083 2.330729-2.617187 4.661458.481771 2.981771.481771 5.091146 0 1.901042-1.015625 1.901042-1.0677085 0-1.0677085-1.992188 0-2.278646.8854165-5.104166-.5729165-2.65625-1.4453123-4.9348961-1.3932292-3.6458333-2.7473958-3.6458333-.9895833 0-1.1458333 2.0182291h-.4557292v-.1822916q0-3.5546874 1.9010416-3.5546874 1.6796875 0 3.0078125 3.9973957.234375.7161458 1.3802085 5.5078126 1.914062-3.997396 2.265625-6.6276043.169271-1.2630208.325521-1.7317707.351562-1.1458333 1.067708-1.1458333.911458 0 .911458.9895833z" fill="#505050"/><rect fill="#4e9a06" height="11" opacity=".9" rx="2.01149" width="11" x="13" y="13"/><path d="m15.5 15.500001 3 6 3-6z" fill="#fff" opacity=".9" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"/><path d="m14 19.000001 9-.0096s0 0 0-2c0-2.9904-1-2.9904-4.5-2.9904s-4.5 0-4.5 3z" fill="#fcffff" fill-rule="evenodd" opacity=".3"/></svg>
@@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><rect fill="#4e9a06" height="11" opacity=".9" rx="2.01149" width="11" x="13" y="13"/><path d="m15.5 21.5 3-6 3 6z" fill="#fff" opacity=".9" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"/><path d="m14 19 9-.0096s0 0 0-2c0-2.9904-1-2.9904-4.5-2.9904s-4.5 0-4.5 3z" fill="#fcffff" fill-rule="evenodd" opacity=".3"/><path d="m15.29989 4.213459q0 1.4192708-1.731771 4.752604-1.302083 2.330729-2.617187 4.661458.481771 2.981771.481771 5.091146 0 1.901042-1.015625 1.901042-1.0677085 0-1.0677085-1.992188 0-2.278646.8854165-5.104166-.5729165-2.65625-1.4453123-4.9348961-1.3932292-3.6458333-2.7473958-3.6458333-.9895833 0-1.1458333 2.0182291h-.4557292v-.1822916q0-3.5546874 1.9010416-3.5546874 1.6796875 0 3.0078125 3.9973957.234375.7161458 1.3802085 5.5078126 1.914062-3.997396 2.265625-6.6276043.169271-1.2630208.325521-1.7317707.351562-1.1458333 1.067708-1.1458333.911458 0 .911458.9895833z" fill="#505050"/></svg>
@@ -13,7 +13,7 @@
class QgsBrightnessContrastFilter : QgsRasterInterface
{
%Docstring
Brightness/contrast filter pipe for rasters.
Brightness/contrast and gamma correction filter pipe for rasters.
%End

%TypeHeaderCode
@@ -24,27 +24,94 @@ Brightness/contrast filter pipe for rasters.

virtual QgsBrightnessContrastFilter *clone() const /Factory/;

%Docstring
Clone itself, create deep copy
%End

virtual int bandCount() const;

%Docstring
Gets number of bands
%End

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

%Docstring
Returns data type for the band specified by number
%End

virtual bool setInput( QgsRasterInterface *input );

%Docstring
Set input.
Returns ``True`` if set correctly, ``False`` if cannot use that input
%End

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

%Docstring
Read block of data using given extent and size.
Returns pointer to data.
Caller is responsible to free the memory returned.

:param bandNo: band number
:param extent: extent of block
:param width: pixel width of block
:param height: pixel height of block
:param feedback: optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
%End

void setBrightness( int brightness );
%Docstring
Set brightness level. Acceptable value range is -255…255

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

int brightness() const;
%Docstring
Returns current brightness level.

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

void setContrast( int contrast );
%Docstring
Set contrast level. Acceptable value range is -100…100

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

int contrast() const;
%Docstring
Returns current contrast level.

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

void setGamma( double gamma );
%Docstring
Set gamma value. Acceptable value range is -0.1…10

.. seealso:: :py:func:`gamma`

.. versionadded:: 3.16
%End

double gamma() const;
%Docstring
Returns current gamma value.

.. seealso:: :py:func:`setGamma`

.. versionadded:: 3.16
%End

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

%Docstring
Write base class members to xml.
%End

virtual void readXml( const QDomElement &filterElem );

@@ -30,7 +30,7 @@ Base class for processing modules.
ResamplerRole,
ProjectorRole,
NullerRole,
HueSaturationRole
HueSaturationRole,
};

QgsRasterPipe();
@@ -2785,6 +2785,8 @@ void QgisApp::createActions()
connect( mActionDecreaseBrightness, &QAction::triggered, this, &QgisApp::decreaseBrightness );
connect( mActionIncreaseContrast, &QAction::triggered, this, &QgisApp::increaseContrast );
connect( mActionDecreaseContrast, &QAction::triggered, this, &QgisApp::decreaseContrast );
connect( mActionIncreaseGamma, &QAction::triggered, this, &QgisApp::increaseGamma );
connect( mActionDecreaseGamma, &QAction::triggered, this, &QgisApp::decreaseGamma );

#ifdef HAVE_GEOREFERENCER
connect( mActionShowGeoreferencer, &QAction::triggered, this, &QgisApp::showGeoreferencer );
@@ -3940,6 +3942,8 @@ void QgisApp::setTheme( const QString &themeName )
mActionDecreaseBrightness->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseBrightness.svg" ) ) );
mActionIncreaseContrast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIncreaseContrast.svg" ) ) );
mActionDecreaseContrast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseContrast.svg" ) ) );
mActionIncreaseGamma->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIncreaseGamma.svg" ) ) );
mActionDecreaseGamma->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseGamma.svg" ) ) );
mActionZoomActualSize->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomNative.png" ) ) );
mActionQgisHomePage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionQgisHomePage.png" ) ) );
mActionAbout->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHelpAbout.svg" ) ) );
@@ -12210,6 +12214,54 @@ void QgisApp::adjustBrightnessContrast( int delta, bool updateBrightness )
}
}

void QgisApp::increaseGamma()
{
double step = 0.1;
if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
{
step = 1.0;
}
adjustGamma( step );
}

void QgisApp::decreaseGamma()
{
double step = -0.1;
if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
{
step = -1.0;
}
adjustGamma( step );
}

void QgisApp::adjustGamma( double delta )
{
const auto constSelectedLayers = mLayerTreeView->selectedLayers();
for ( QgsMapLayer *layer : constSelectedLayers )
{
if ( !layer )
{
visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
tr( "To change gamma, you need to have a raster layer selected." ),
Qgis::Info, messageTimeout() );
return;
}

QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
if ( !rasterLayer )
{
visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
tr( "To change gamma, you need to have a raster layer selected." ),
Qgis::Info, messageTimeout() );
return;
}

QgsBrightnessContrastFilter *brightnessFilter = rasterLayer->brightnessFilter();
brightnessFilter->setGamma( brightnessFilter->gamma() + delta );

rasterLayer->triggerRepaint();
}
}

void QgisApp::helpContents()
{
@@ -1297,6 +1297,22 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
* Decrease raster contrast
* Valid for non wms raster layers only. */
void decreaseContrast();

/**
* Increase raster gamma
* Valid for non wms raster layers only.
* \since QGIS 3.16
*/
void increaseGamma();

/**
* Decrease raster gamma
* Valid for non wms raster layers only.
* \since QGIS 3.16
*/
void decreaseGamma();


//! plugin manager
void showPluginManager();
//! load Python support if possible
@@ -2104,9 +2120,15 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Do histogram stretch for singleband gray / multiband color rasters
void histogramStretch( bool visibleAreaOnly = false, QgsRasterMinMaxOrigin::Limits limits = QgsRasterMinMaxOrigin::MinMax );

//! Apply raster brightness
//! Apply raster brightness/contrast
void adjustBrightnessContrast( int delta, bool updateBrightness = true );

/**
* Apply raster gamma
* \since QGIS 3.16
*/
void adjustGamma( double delta );

//! Copy a vector style from a layer to another one, if they have the same geometry type
void duplicateVectorStyle( QgsVectorLayer *srcLayer, QgsVectorLayer *destLayer );

@@ -32,6 +32,7 @@ QgsBrightnessContrastFilter *QgsBrightnessContrastFilter::clone() const
QgsBrightnessContrastFilter *filter = new QgsBrightnessContrastFilter( nullptr );
filter->setBrightness( mBrightness );
filter->setContrast( mContrast );
filter->setGamma( mGamma );
return filter;
}

@@ -122,9 +123,9 @@ QgsRasterBlock *QgsBrightnessContrastFilter::block( int bandNo, QgsRectangle co
return outputBlock.release();
}

if ( mBrightness == 0 && mContrast == 0 )
if ( mBrightness == 0 && mContrast == 0 && mGamma == 1.0 )
{
QgsDebugMsgLevel( QStringLiteral( "No brightness changes." ), 4 );
QgsDebugMsgLevel( QStringLiteral( "No brightness/contrast/gamma changes." ), 4 );
return inputBlock.release();
}

@@ -139,6 +140,7 @@ QgsRasterBlock *QgsBrightnessContrastFilter::block( int bandNo, QgsRectangle co

int r, g, b, alpha;
double f = std::pow( ( mContrast + 100 ) / 100.0, 2 );
double gammaCorrection = 1.0 / mGamma;

for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
{
@@ -151,22 +153,22 @@ QgsRasterBlock *QgsBrightnessContrastFilter::block( int bandNo, QgsRectangle co
myColor = inputBlock->color( i );
alpha = qAlpha( myColor );

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

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

return outputBlock.release();
}

int QgsBrightnessContrastFilter::adjustColorComponent( int colorComponent, int alpha, int brightness, double contrastFactor ) const
int QgsBrightnessContrastFilter::adjustColorComponent( int colorComponent, int alpha, int brightness, double contrastFactor, double gammaCorrection ) const
{
if ( alpha == 255 )
{
// Opaque pixel, do simpler math
return qBound( 0, ( int )( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 255 );
return qBound( 0, ( int )( 255 * std::pow( ( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ) / 255.0, gammaCorrection ) ), 255 );
}
else if ( alpha == 0 )
{
@@ -181,7 +183,7 @@ int QgsBrightnessContrastFilter::adjustColorComponent( int colorComponent, int a
double adjustedColor = colorComponent / alphaFactor;

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

@@ -196,6 +198,7 @@ void QgsBrightnessContrastFilter::writeXml( QDomDocument &doc, QDomElement &pare

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

@@ -208,4 +211,5 @@ void QgsBrightnessContrastFilter::readXml( const QDomElement &filterElem )

mBrightness = filterElem.attribute( QStringLiteral( "brightness" ), QStringLiteral( "0" ) ).toInt();
mContrast = filterElem.attribute( QStringLiteral( "contrast" ), QStringLiteral( "0" ) ).toInt();
mGamma = filterElem.attribute( QStringLiteral( "gamma" ), QStringLiteral( "1" ) ).toDouble();
}

0 comments on commit 6068c64

Please sign in to comment.
You can’t perform that action at this time.