Skip to content

Commit

Permalink
Implement responsive cancelation for more paint effect operations
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Sep 24, 2021
1 parent 07239ea commit 806735c
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 61 deletions.
15 changes: 10 additions & 5 deletions python/core/auto_generated/effects/qgsimageoperation.sip.in
Expand Up @@ -46,15 +46,16 @@ on which is faster for the particular operation.
FlipVertical
};

static void convertToGrayscale( QImage &image, GrayscaleMode mode = GrayscaleLuminosity );
static void convertToGrayscale( QImage &image, GrayscaleMode mode = GrayscaleLuminosity, QgsFeedback *feedback = 0 );
%Docstring
Convert a QImage to a grayscale image. Alpha channel is preserved.

:param image: QImage to convert
:param mode: mode to use during grayscale conversion
:param feedback: optional feedback object for responsive cancelation (since QGIS 3.22)
%End

static void adjustBrightnessContrast( QImage &image, int brightness, double contrast );
static void adjustBrightnessContrast( QImage &image, int brightness, double contrast, QgsFeedback *feedback = 0 );
%Docstring
Alter the brightness or contrast of a QImage.

Expand All @@ -65,10 +66,11 @@ Alter the brightness or contrast of a QImage.
:param contrast: contrast value. Must be a positive or zero value. A value of 1.0 indicates no change
to the contrast, a value of 0 represents an image with 0 contrast, and a value > 1.0 will increase the
contrast of the image.
:param feedback: optional feedback object for responsive cancelation (since QGIS 3.22)
%End

static void adjustHueSaturation( QImage &image, double saturation, const QColor &colorizeColor = QColor(),
double colorizeStrength = 1.0 );
double colorizeStrength = 1.0, QgsFeedback *feedback = 0 );
%Docstring
Alter the hue or saturation of a QImage.

Expand All @@ -77,14 +79,16 @@ Alter the hue or saturation of a QImage.
:param colorizeColor: color to use for colorizing image. Set to an invalid QColor to disable
colorization.
:param colorizeStrength: double between 0 and 1, where 0 = no colorization and 1.0 = full colorization
:param feedback: optional feedback object for responsive cancelation (since QGIS 3.22)
%End

static void multiplyOpacity( QImage &image, double factor );
static void multiplyOpacity( QImage &image, double factor, QgsFeedback *feedback = 0 );
%Docstring
Multiplies opacity of image pixel values by a factor.

:param image: QImage to alter
:param factor: factor to multiple pixel's opacity by
:param feedback: optional feedback object for responsive cancelation (since QGIS 3.22)
%End

static void overlayColor( QImage &image, const QColor &color );
Expand All @@ -108,14 +112,15 @@ original image, but replaces all image pixel colors with the specified color.
QgsColorRamp *ramp;
};

static void distanceTransform( QImage &image, const QgsImageOperation::DistanceTransformProperties &properties );
static void distanceTransform( QImage &image, const QgsImageOperation::DistanceTransformProperties &properties, QgsFeedback *feedback = 0 );
%Docstring
Performs a distance transform on the source image and shades the result
using a color ramp.

:param image: QImage to alter
:param properties: DistanceTransformProperties object with parameters
for the distance transform operation
:param feedback: optional feedback object for responsive cancelation (since QGIS 3.22)
%End

static void stackBlur( QImage &image, int radius, bool alphaOnly = false, QgsFeedback *feedback = 0 );
Expand Down
2 changes: 1 addition & 1 deletion src/core/effects/qgsblureffect.cpp
Expand Up @@ -63,7 +63,7 @@ void QgsBlurEffect::drawGaussianBlur( QgsRenderContext &context )
void QgsBlurEffect::drawBlurredImage( QgsRenderContext &context, QImage &image )
{
//opacity
QgsImageOperation::multiplyOpacity( image, mOpacity );
QgsImageOperation::multiplyOpacity( image, mOpacity, context.feedback() );

QPainter *painter = context.painter();
const QgsScopedQPainterState painterState( painter );
Expand Down
23 changes: 19 additions & 4 deletions src/core/effects/qgscoloreffect.cpp
Expand Up @@ -43,14 +43,29 @@ void QgsColorEffect::draw( QgsRenderContext &context )
//rasterize source and apply modifications
QImage image = sourceAsImage( context )->copy();

QgsImageOperation::adjustBrightnessContrast( image, mBrightness, mContrast / 100.0 + 1 );
QgsImageOperation::adjustBrightnessContrast( image, mBrightness, mContrast / 100.0 + 1, context.feedback() );

if ( context.feedback() && context.feedback()->isCanceled() )
return;

if ( mGrayscaleMode != QgsImageOperation::GrayscaleOff )
{
QgsImageOperation::convertToGrayscale( image, static_cast< QgsImageOperation::GrayscaleMode >( mGrayscaleMode ) );
QgsImageOperation::convertToGrayscale( image, static_cast< QgsImageOperation::GrayscaleMode >( mGrayscaleMode ), context.feedback() );
}
QgsImageOperation::adjustHueSaturation( image, mSaturation, mColorizeOn ? mColorizeColor : QColor(), mColorizeStrength / 100.0 );

QgsImageOperation::multiplyOpacity( image, mOpacity );
if ( context.feedback() && context.feedback()->isCanceled() )
return;

QgsImageOperation::adjustHueSaturation( image, mSaturation, mColorizeOn ? mColorizeColor : QColor(), mColorizeStrength / 100.0, context.feedback() );

if ( context.feedback() && context.feedback()->isCanceled() )
return;

QgsImageOperation::multiplyOpacity( image, mOpacity, context.feedback() );

if ( context.feedback() && context.feedback()->isCanceled() )
return;

QgsScopedQPainterState painterState( painter );
painter->setCompositionMode( mBlendMode );
painter->drawImage( imageOffset( context ), image );
Expand Down
13 changes: 11 additions & 2 deletions src/core/effects/qgsgloweffect.cpp
Expand Up @@ -65,7 +65,10 @@ void QgsGlowEffect::draw( QgsRenderContext &context )
dtProps.useMaxDistance = false;
dtProps.shadeExterior = shadeExterior();
dtProps.ramp = ramp;
QgsImageOperation::distanceTransform( im, dtProps );
QgsImageOperation::distanceTransform( im, dtProps, context.feedback() );

if ( context.feedback() && context.feedback()->isCanceled() )
return;

const int blurLevel = std::round( context.convertToPainterUnits( mBlurLevel, mBlurUnit, mBlurMapUnitScale ) );
if ( blurLevel <= 16 )
Expand All @@ -80,7 +83,13 @@ void QgsGlowEffect::draw( QgsRenderContext &context )
delete imb;
}

QgsImageOperation::multiplyOpacity( im, mOpacity );
if ( context.feedback() && context.feedback()->isCanceled() )
return;

QgsImageOperation::multiplyOpacity( im, mOpacity, context.feedback() );

if ( context.feedback() && context.feedback()->isCanceled() )
return;

if ( !shadeExterior() )
{
Expand Down
81 changes: 47 additions & 34 deletions src/core/effects/qgsimageoperation.cpp
Expand Up @@ -33,29 +33,32 @@
/// @cond PRIVATE

template <typename PixelOperation>
void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation )
void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation, QgsFeedback *feedback )
{
if ( image.height() * image.width() < 100000 )
if ( static_cast< qgssize >( image.height() ) * image.width() < 100000 )
{
//small image, don't multithread
//this threshold was determined via testing various images
runPixelOperationOnWholeImage( image, operation );
runPixelOperationOnWholeImage( image, operation, feedback );
}
else
{
//large image, multithread operation
QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation );
QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation, feedback );
runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
}
}

template <typename PixelOperation>
void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation )
void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation, QgsFeedback *feedback )
{
int height = image.height();
int width = image.width();
for ( int y = 0; y < height; ++y )
{
if ( feedback && feedback->isCanceled() )
break;

QRgb *ref = reinterpret_cast< QRgb * >( image.scanLine( y ) );
for ( int x = 0; x < width; ++x )
{
Expand Down Expand Up @@ -183,15 +186,15 @@ void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperatio

//grayscale

void QgsImageOperation::convertToGrayscale( QImage &image, const GrayscaleMode mode )
void QgsImageOperation::convertToGrayscale( QImage &image, const GrayscaleMode mode, QgsFeedback *feedback )
{
if ( mode == GrayscaleOff )
{
return;
}

GrayscalePixelOperation operation( mode );
runPixelOperation( image, operation );
runPixelOperation( image, operation, feedback );
}

void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb, const int x, const int y )
Expand Down Expand Up @@ -243,10 +246,10 @@ void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )

//brightness/contrast

void QgsImageOperation::adjustBrightnessContrast( QImage &image, const int brightness, const double contrast )
void QgsImageOperation::adjustBrightnessContrast( QImage &image, const int brightness, const double contrast, QgsFeedback *feedback )
{
BrightnessContrastPixelOperation operation( brightness, contrast );
runPixelOperation( image, operation );
runPixelOperation( image, operation, feedback );
}

void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb, const int x, const int y )
Expand All @@ -266,11 +269,11 @@ int QgsImageOperation::adjustColorComponent( int colorComponent, int brightness,

//hue/saturation

void QgsImageOperation::adjustHueSaturation( QImage &image, const double saturation, const QColor &colorizeColor, const double colorizeStrength )
void QgsImageOperation::adjustHueSaturation( QImage &image, const double saturation, const QColor &colorizeColor, const double colorizeStrength, QgsFeedback *feedback )
{
HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0,
colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
runPixelOperation( image, operation );
runPixelOperation( image, operation, feedback );
}

void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb, const int x, const int y )
Expand Down Expand Up @@ -320,7 +323,7 @@ void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb, cons

//multiply opacity

void QgsImageOperation::multiplyOpacity( QImage &image, const double factor )
void QgsImageOperation::multiplyOpacity( QImage &image, const double factor, QgsFeedback *feedback )
{
if ( qgsDoubleNear( factor, 1.0 ) )
{
Expand All @@ -341,7 +344,7 @@ void QgsImageOperation::multiplyOpacity( QImage &image, const double factor )
{
//increasing opacity - run this as a pixel operation for multithreading
MultiplyOpacityPixelOperation operation( factor );
runPixelOperation( image, operation );
runPixelOperation( image, operation, feedback );
}
}

Expand Down Expand Up @@ -369,7 +372,7 @@ void QgsImageOperation::overlayColor( QImage &image, const QColor &color )

// distance transform

void QgsImageOperation::distanceTransform( QImage &image, const DistanceTransformProperties &properties )
void QgsImageOperation::distanceTransform( QImage &image, const DistanceTransformProperties &properties, QgsFeedback *feedback )
{
if ( ! properties.ramp )
{
Expand All @@ -378,27 +381,36 @@ void QgsImageOperation::distanceTransform( QImage &image, const DistanceTransfor
}

//first convert to 1 bit alpha mask array
double *array = new double[ static_cast< qgssize >( image.width() ) * image.height()];
ConvertToArrayPixelOperation convertToArray( image.width(), array, properties.shadeExterior );
runPixelOperation( image, convertToArray );
std::unique_ptr<double[]> array( new double[ static_cast< qgssize >( image.width() ) * image.height()] );
if ( feedback && feedback->isCanceled() )
return;

ConvertToArrayPixelOperation convertToArray( image.width(), array.get(), properties.shadeExterior );
runPixelOperation( image, convertToArray, feedback );
if ( feedback && feedback->isCanceled() )
return;

//calculate distance transform (single threaded only)
distanceTransform2d( array, image.width(), image.height() );
distanceTransform2d( array.get(), image.width(), image.height(), feedback );
if ( feedback && feedback->isCanceled() )
return;

double spread;
if ( properties.useMaxDistance )
{
spread = std::sqrt( maxValueInDistanceTransformArray( array, image.width() * image.height() ) );
spread = std::sqrt( maxValueInDistanceTransformArray( array.get(), image.width() * image.height() ) );
}
else
{
spread = properties.spread;
}

if ( feedback && feedback->isCanceled() )
return;

//shade distance transform
ShadeFromArrayOperation shadeFromArray( image.width(), array, spread, properties );
runPixelOperation( image, shadeFromArray );
delete [] array;
ShadeFromArrayOperation shadeFromArray( image.width(), array.get(), spread, properties );
runPixelOperation( image, shadeFromArray, feedback );
}

void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb, const int x, const int y )
Expand Down Expand Up @@ -477,23 +489,26 @@ double QgsImageOperation::maxValueInDistanceTransformArray( const double *array,
}

/* distance transform of 2d function using squared distance */
void QgsImageOperation::distanceTransform2d( double *im, int width, int height )
void QgsImageOperation::distanceTransform2d( double *im, int width, int height, QgsFeedback *feedback )
{
int maxDimension = std::max( width, height );

double *f = new double[ maxDimension ];
int *v = new int[ maxDimension ];
double *z = new double[ maxDimension + 1 ];
double *d = new double[ maxDimension ];
std::unique_ptr<double[]> f( new double[ maxDimension ] );
std::unique_ptr<int []> v( new int[ maxDimension ] );
std::unique_ptr<double[]>z( new double[ maxDimension + 1 ] );
std::unique_ptr<double[]>d( new double[ maxDimension ] );

// transform along columns
for ( int x = 0; x < width; x++ )
{
if ( feedback && feedback->isCanceled() )
break;

for ( int y = 0; y < height; y++ )
{
f[y] = im[ x + y * width ];
}
distanceTransform1d( f, height, v, z, d );
distanceTransform1d( f.get(), height, v.get(), z.get(), d.get() );
for ( int y = 0; y < height; y++ )
{
im[ x + y * width ] = d[y];
Expand All @@ -503,21 +518,19 @@ void QgsImageOperation::distanceTransform2d( double *im, int width, int height )
// transform along rows
for ( int y = 0; y < height; y++ )
{
if ( feedback && feedback->isCanceled() )
break;

for ( int x = 0; x < width; x++ )
{
f[x] = im[ x + y * width ];
}
distanceTransform1d( f, width, v, z, d );
distanceTransform1d( f.get(), width, v.get(), z.get(), d.get() );
for ( int x = 0; x < width; x++ )
{
im[ x + y * width ] = d[x];
}
}

delete [] d;
delete [] f;
delete [] v;
delete [] z;
}

void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb, const int x, const int y )
Expand Down

0 comments on commit 806735c

Please sign in to comment.