Skip to content

Commit

Permalink
Raster resampling: add provider-level settings, and a switch at raste…
Browse files Browse the repository at this point in the history
…rpipe level between resamplingFilter and provider resampling

Also
* in settings UI, add a 'Early resampling' checkbox
* in raster properties UI, add similar checkbox
* serialize/deserialize new settings in QgsRasterLayer XML
  • Loading branch information
rouault authored and nyalldawson committed Jun 19, 2020
1 parent f32026c commit 47da98c
Show file tree
Hide file tree
Showing 19 changed files with 650 additions and 225 deletions.
97 changes: 96 additions & 1 deletion python/core/auto_generated/raster/qgsrasterdataprovider.sip.in
Expand Up @@ -75,7 +75,8 @@ Base class for raster data providers.
NoProviderCapabilities,
ReadLayerMetadata,
WriteLayerMetadata,
ProviderHintBenefitsFromResampling
ProviderHintBenefitsFromResampling,
ProviderHintCanPerformProviderResampling
};

typedef QFlags<QgsRasterDataProvider::ProviderCapability> ProviderCapabilities;
Expand Down Expand Up @@ -516,6 +517,96 @@ does not support this functionality), an empty point is returned.
.. versionadded:: 3.14
%End


virtual bool enableProviderResampling( bool enable );
%Docstring
Enable or disable provider-level resampling.

:return: true if success

.. versionadded:: 3.16
%End

bool isProviderResamplingEnabled() const;
%Docstring
Returns whether provider-level resampling is enabled.

.. note::

Resampling is effective only if :py:func:`~QgsRasterDataProvider.zoomedInResamplingMethod` and/or
:py:func:`~QgsRasterDataProvider.zoomedOutResamplingMethod` return non-nearest resampling.

.. seealso:: :py:func:`zoomedInResamplingMethod`

.. seealso:: :py:func:`zoomedOutResamplingMethod`

.. seealso:: :py:func:`maxOversampling`


.. versionadded:: 3.16
%End

enum class ResamplingMethod
{
Nearest,
Bilinear,
Cubic,
};

virtual bool setZoomedInResamplingMethod( ResamplingMethod method );
%Docstring
Set resampling method to apply for zoomed-in operations.

:return: true if success

.. versionadded:: 3.16
%End

ResamplingMethod zoomedInResamplingMethod() const;
%Docstring
Returns resampling method for zoomed-in operations.

.. versionadded:: 3.16
%End

virtual bool setZoomedOutResamplingMethod( ResamplingMethod method );
%Docstring
Set resampling method to apply for zoomed-out operations.

:return: true if success

.. versionadded:: 3.16
%End

ResamplingMethod zoomedOutResamplingMethod() const;
%Docstring
Returns resampling method for zoomed-out operations.

.. versionadded:: 3.16
%End

virtual bool setMaxOversampling( double factor );
%Docstring
Sets maximum oversampling factor for zoomed-out operations.

:return: true if success

.. versionadded:: 3.16
%End

double maxOversampling() const;
%Docstring
Returns maximum oversampling factor for zoomed-out operations.

.. versionadded:: 3.16
%End

virtual void readXml( const QDomElement &filterElem );


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


signals:

void statusChanged( const QString & ) const;
Expand Down Expand Up @@ -546,6 +637,10 @@ Copy member variables from other raster data provider. Useful for implementation







};

QFlags<QgsRasterDataProvider::ProviderCapability> operator|(QgsRasterDataProvider::ProviderCapability f1, QFlags<QgsRasterDataProvider::ProviderCapability> f2);
Expand Down
4 changes: 0 additions & 4 deletions python/core/auto_generated/raster/qgsrasterinterface.sip.in
Expand Up @@ -11,7 +11,6 @@




class QgsRasterBlockFeedback : QgsFeedback
{
%Docstring
Expand Down Expand Up @@ -83,8 +82,6 @@ Returns a list of any errors encountered while retrieving the raster block.
.. versionadded:: 3.8.0
%End



};


Expand Down Expand Up @@ -241,7 +238,6 @@ Caller is responsible to free the memory returned.
:param feedback: optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
%End


virtual bool setInput( QgsRasterInterface *input );
%Docstring
Set input.
Expand Down
18 changes: 18 additions & 0 deletions python/core/auto_generated/raster/qgsrasterlayer.sip.in
Expand Up @@ -209,6 +209,24 @@ Returns the raster's hue/saturation filter.
.. seealso:: :py:func:`resampleFilter`

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

void setResamplingStage( QgsRasterPipe::ResamplingStage stage );
%Docstring
Select which stage of the pipe should apply resampling.

.. seealso:: :py:func:`QgsRasterPipe.setResamplingStage`

.. versionadded:: 3.16
%End

QgsRasterPipe::ResamplingStage resamplingStage() const;
%Docstring
Returns which stage of the pipe should apply resampling.

.. seealso:: :py:func:`QgsRasterPipe.resamplingStage`

.. versionadded:: 3.16
%End

QgsRasterPipe *pipe();
Expand Down
25 changes: 25 additions & 0 deletions python/core/auto_generated/raster/qgsrasterpipe.sip.in
Expand Up @@ -106,6 +106,31 @@ Test if interface at index may be switched on/off
QgsRasterProjector *projector() const;
QgsRasterNuller *nuller() const;

enum class ResamplingStage
{
//! Resampling occurs in ResamplingFilter
ResampleFilter,
//! Resampling occurs in Provider
Provider
};

void setResamplingStage( ResamplingStage stage );
%Docstring
Select which stage of the pipe should apply resampling.

Provider resampling is only supported if provider sets
ProviderHintCanPerformProviderResampling in :py:func:`~QgsRasterPipe.providerCapabilities`.

.. versionadded:: 3.16
%End

ResamplingStage resamplingStage() const;
%Docstring
Returns which stage of the pipe should apply resampling

.. versionadded:: 3.16
%End

private:
QgsRasterPipe( const QgsRasterPipe &pipe );
};
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgsoptions.cpp
Expand Up @@ -721,6 +721,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
QString zoomedOutResampling = mSettings->value( QStringLiteral( "/Raster/defaultZoomedOutResampling" ), QStringLiteral( "nearest neighbour" ) ).toString();
mZoomedOutResamplingComboBox->setCurrentIndex( mZoomedOutResamplingComboBox->findData( zoomedOutResampling ) );
spnOversampling->setValue( mSettings->value( QStringLiteral( "/Raster/defaultOversampling" ), 2.0 ).toDouble() );
mCbEarlyResampling->setChecked( mSettings->value( QStringLiteral( "/Raster/defaultEarlyResampling" ), false ).toBool() );

initContrastEnhancement( cboxContrastEnhancementAlgorithmSingleBand, QStringLiteral( "singleBand" ),
QgsContrastEnhancement::contrastEnhancementAlgorithmString( QgsRasterLayer::SINGLE_BAND_ENHANCEMENT_ALGORITHM ) );
Expand Down Expand Up @@ -1595,6 +1596,7 @@ void QgsOptions::saveOptions()
mSettings->setValue( QStringLiteral( "/Raster/defaultZoomedInResampling" ), mZoomedInResamplingComboBox->currentData().toString() );
mSettings->setValue( QStringLiteral( "/Raster/defaultZoomedOutResampling" ), mZoomedOutResamplingComboBox->currentData().toString() );
mSettings->setValue( QStringLiteral( "/Raster/defaultOversampling" ), spnOversampling->value() );
mSettings->setValue( QStringLiteral( "/Raster/defaultEarlyResampling" ), mCbEarlyResampling->isChecked() );

saveContrastEnhancement( cboxContrastEnhancementAlgorithmSingleBand, QStringLiteral( "singleBand" ) );
saveContrastEnhancement( cboxContrastEnhancementAlgorithmMultiBandSingleByte, QStringLiteral( "multiBandSingleByte" ) );
Expand Down
63 changes: 28 additions & 35 deletions src/core/providers/gdal/qgsgdalprovider.cpp
Expand Up @@ -44,10 +44,6 @@
#include "qgssettings.h"
#include "qgsogrutils.h"

#include "qgsrasterresamplefilter.h"
#include "qgsbilinearrasterresampler.h"
#include "qgscubicrasterresampler.h"

#include <QImage>
#include <QColor>
#include <QProcess>
Expand Down Expand Up @@ -718,19 +714,17 @@ bool QgsGdalProvider::readBlock( int bandNo, int xBlock, int yBlock, void *data
return true;
}

bool QgsGdalProvider::canHandleBlockRequestWithResampling(
bool QgsGdalProvider::canDoResampling(
int bandNo,
const QgsRectangle &reqExtent,
int bufferWidthPix,
int bufferHeightPix,
const QgsRasterResampleFilter *filter )
int bufferHeightPix )
{
QMutexLocker locker( mpMutex );
if ( !initIfNeeded() )
if ( !mProviderResamplingEnabled )
return false;

// Hidden property, just/mostly for the needs of test_qgsrasterresampler.py
if ( property( "skip_gdal_resampling" ).isValid() )
QMutexLocker locker( mpMutex );
if ( !initIfNeeded() )
return false;

GDALRasterBandH gdalBand = getBand( bandNo );
Expand All @@ -748,30 +742,26 @@ bool QgsGdalProvider::canHandleBlockRequestWithResampling(

if ( resamplingFactor < 1 )
{
// upsampling ==> check compatibility of zoom-in resampler with what GDAL can do
return dynamic_cast<const QgsBilinearRasterResampler *>( filter->zoomedInResampler() ) != nullptr ||
dynamic_cast<const QgsCubicRasterResampler *>( filter->zoomedInResampler() ) != nullptr;
// upsampling
return mZoomedInResamplingMethod != QgsRasterDataProvider::ResamplingMethod::Nearest;
}

if ( resamplingFactor < 1.1 )
{
// very close to nominal resolution ==> check compatibility of zoom-in or zoom-out resampler with what GDAL can do
return dynamic_cast<const QgsBilinearRasterResampler *>( filter->zoomedInResampler() ) != nullptr ||
dynamic_cast<const QgsCubicRasterResampler *>( filter->zoomedInResampler() ) != nullptr ||
dynamic_cast<const QgsBilinearRasterResampler *>( filter->zoomedOutResampler() ) != nullptr ||
dynamic_cast<const QgsCubicRasterResampler *>( filter->zoomedOutResampler() ) != nullptr;
return mZoomedInResamplingMethod != QgsRasterDataProvider::ResamplingMethod::Nearest ||
mZoomedOutResamplingMethod != QgsRasterDataProvider::ResamplingMethod::Nearest;
}

// if zoomedOutResampler is not compatible of GDAL, exit now
if ( dynamic_cast<const QgsBilinearRasterResampler *>( filter->zoomedOutResampler() ) == nullptr &&
dynamic_cast<const QgsCubicRasterResampler *>( filter->zoomedOutResampler() ) == nullptr )
// if no zoom out resampling, exit now
if ( mZoomedOutResamplingMethod == QgsRasterDataProvider::ResamplingMethod::Nearest )
{
return false;
}

// if the resampling factor is not too large, we can do the downsampling
// from the full resolution with acceptable performance
if ( resamplingFactor <= ( filter->maxOversampling() + .1 ) )
if ( resamplingFactor <= ( mMaxOversampling + .1 ) )
{
return true;
}
Expand All @@ -783,7 +773,7 @@ bool QgsGdalProvider::canHandleBlockRequestWithResampling(
{
GDALRasterBandH hOvrBand = GDALGetOverview( gdalBand, i );
const double ovrResamplingFactor = xSize() / static_cast<double>( GDALGetRasterBandXSize( hOvrBand ) );
if ( resamplingFactor <= ( filter->maxOversampling() + .1 ) * ovrResamplingFactor )
if ( resamplingFactor <= ( mMaxOversampling + .1 ) * ovrResamplingFactor )
{
return true;
}
Expand All @@ -793,6 +783,7 @@ bool QgsGdalProvider::canHandleBlockRequestWithResampling(
return false;
}


bool QgsGdalProvider::readBlock( int bandNo, QgsRectangle const &reqExtent, int bufferWidthPix, int bufferHeightPix, void *data, QgsRasterBlockFeedback *feedback )
{
QMutexLocker locker( mpMutex );
Expand Down Expand Up @@ -868,9 +859,8 @@ bool QgsGdalProvider::readBlock( int bandNo, QgsRectangle const &reqExtent, int
const int srcWidth = srcRight - srcLeft + 1;
const int srcHeight = srcBottom - srcTop + 1;

// Use GDAL cubic/bilinear resampling if asked
const QgsRasterResampleFilter *filter = feedback != nullptr ? feedback->resampleFilter() : nullptr;
if ( filter )
// Use GDAL resampling if asked and possible
if ( canDoResampling( bandNo, reqExtent, bufferWidthPix, bufferHeightPix ) )
{
int tgtTop = tgtTopOri;
int tgtBottom = tgtBottomOri;
Expand Down Expand Up @@ -938,27 +928,29 @@ bool QgsGdalProvider::readBlock( int bandNo, QgsRectangle const &reqExtent, int
INIT_RASTERIO_EXTRA_ARG( sExtraArg );

const double resamplingFactor = std::max( reqXRes / srcXRes, reqYRes / std::fabs( srcYRes ) );
const QgsRasterResampler *resampler = nullptr;
ResamplingMethod method;
if ( resamplingFactor < 1 )
{
resampler = filter->zoomedInResampler();
method = mZoomedInResamplingMethod;
}
else if ( resamplingFactor < 1.1 )
{
// very close to nominal resolution ==> use either zoomed out resampler or zoomed in resampler
if ( filter->zoomedOutResampler() )
resampler = filter->zoomedOutResampler();
if ( mZoomedOutResamplingMethod != ResamplingMethod::Nearest )
method = mZoomedOutResamplingMethod;
else
resampler = filter->zoomedInResampler();
method = mZoomedInResamplingMethod;
}
else
{
resampler = filter->zoomedOutResampler();
method = mZoomedOutResamplingMethod;
}
if ( dynamic_cast<const QgsCubicRasterResampler *>( resampler ) != nullptr )
if ( method == ResamplingMethod::Bilinear )
sExtraArg.eResampleAlg = GRIORA_Bilinear;
else if ( method == ResamplingMethod::Cubic )
sExtraArg.eResampleAlg = GRIORA_Cubic;
else
sExtraArg.eResampleAlg = GRIORA_Bilinear;
sExtraArg.eResampleAlg = GRIORA_NearestNeighbour;

sExtraArg.bFloatingPointWindowValidity = true;
sExtraArg.dfXOff = ( reqExtent.xMinimum() + tgtLeft * reqXRes - mExtent.xMinimum() ) / srcXRes;
Expand Down Expand Up @@ -1521,7 +1513,8 @@ QString QgsGdalProvider::description() const

QgsRasterDataProvider::ProviderCapabilities QgsGdalProvider::providerCapabilities() const
{
return QgsRasterDataProvider::ProviderHintBenefitsFromResampling;
return QgsRasterDataProvider::ProviderHintBenefitsFromResampling |
QgsRasterDataProvider::ProviderHintCanPerformProviderResampling;
}

// This is used also by global isValidRasterFileName
Expand Down
12 changes: 11 additions & 1 deletion src/core/providers/gdal/qgsgdalprovider.h
Expand Up @@ -147,7 +147,6 @@ class QgsGdalProvider final: public QgsRasterDataProvider, QgsGdalProviderBase

// Reimplemented from QgsRasterDataProvider to bypass second resampling (more efficient for local file based sources)
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback = nullptr ) override;
bool canHandleBlockRequestWithResampling( int bandNo, const QgsRectangle &extent, int width, int height, const QgsRasterResampleFilter *filter ) override;

bool readBlock( int bandNo, int xBlock, int yBlock, void *data ) override;
bool readBlock( int bandNo, QgsRectangle const &viewExtent, int width, int height, void *data, QgsRasterBlockFeedback *feedback = nullptr ) override;
Expand Down Expand Up @@ -207,6 +206,11 @@ class QgsGdalProvider final: public QgsRasterDataProvider, QgsGdalProviderBase

QgsPoint transformCoordinates( const QgsPoint &point, TransformType type ) override;

bool enableProviderResampling( bool enable ) override { mProviderResamplingEnabled = enable; return true; }
bool setZoomedInResamplingMethod( ResamplingMethod method ) override { mZoomedInResamplingMethod = method; return true; }
bool setZoomedOutResamplingMethod( ResamplingMethod method ) override { mZoomedOutResamplingMethod = method; return true; }
bool setMaxOversampling( double factor ) override { mMaxOversampling = factor; return true; }

private:
QgsGdalProvider( const QgsGdalProvider &other );
QgsGdalProvider &operator=( const QgsGdalProvider & ) = delete;
Expand Down Expand Up @@ -346,6 +350,12 @@ class QgsGdalProvider final: public QgsRasterDataProvider, QgsGdalProviderBase

//! Instance of GDAL transformer function used in transformCoordinates() for conversion between image and layer coordinates
void *mGdalTransformerArg = nullptr;

bool canDoResampling(
int bandNo,
const QgsRectangle &reqExtent,
int bufferWidthPix,
int bufferHeightPix );
};

/**
Expand Down

0 comments on commit 47da98c

Please sign in to comment.