Skip to content

Commit

Permalink
Add gaussian blur image operation
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jan 21, 2015
1 parent be2d6d1 commit 670ad7b
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 2 deletions.
20 changes: 19 additions & 1 deletion python/core/effects/qgsimageoperation.sip
Expand Up @@ -3,7 +3,17 @@
* \brief Contains operations and filters which apply to QImages
*
* A set of optimised pixel manipulation operations and filters which can be applied
* to QImages.
* to QImages. All operations only apply to ARGB32 format images, and it is left up
* to the calling procedure to ensure that any passed images are of the correct
* format.
*
* Operations are written to either modify an image in place or return a new image, depending
* on which is faster for the particular operation.
*
* \note These operations do not work using premultiplied ARGB32_Premultiplied images
* - please make sure the images are converted to standard ARGB32 images prior to calling
* these operations.
*
* \note Added in version 2.7
*/
class QgsImageOperation
Expand Down Expand Up @@ -108,6 +118,14 @@ class QgsImageOperation
*/
static void stackBlur( QImage &image, const int radius, const bool alphaOnly = false );

/**Performs a gaussian blur on an image. Gaussian blur is slower but results in a high
* quality blur.
* @param image QImage to blur
* @param radius blur radius in pixels
* @returns blurred image
*/
static QImage* gaussianBlur( QImage &image, const int radius ) /Factory/;

/**Flips an image horizontally or vertically
* @param image QImage to flip
* @param type type of flip to perform (horizontal or vertical)
Expand Down
180 changes: 180 additions & 0 deletions src/core/effects/qgsimageoperation.cpp
Expand Up @@ -62,6 +62,36 @@ void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOpera
}
}

//rect operations

template <typename RectOperation>
void QgsImageOperation::runRectOperation( QImage &image, RectOperation& operation )
{
//possibly could be tweaked for rect operations
if ( image.height() * image.width() < 100000 )
{
//small image, don't multithread
//this threshold was determined via testing various images
runRectOperationOnWholeImage( image, operation );
}
else
{
//large image, multithread operation
runBlockOperationInThreads( image, operation, ByRow );
}
}

template <class RectOperation>
void QgsImageOperation::runRectOperationOnWholeImage( QImage &image, RectOperation& operation )
{
ImageBlock fullImage;
fullImage.beginLine = 0;
fullImage.endLine = image.height();
fullImage.lineLength = image.width();
fullImage.image = &image;

operation( fullImage );
}

//linear operations

Expand Down Expand Up @@ -545,6 +575,152 @@ void QgsImageOperation::StackBlurLineOperation::operator()( QRgb* startRef, cons
}
}

//gaussian blur

QImage *QgsImageOperation::gaussianBlur( QImage &image, const int radius )
{
int width = image.width();
int height = image.height();

if ( radius <= 0 )
{
//just make an unchanged copy
QImage* copy = new QImage( image.copy() );
return copy;
}

double* kernel = createGaussianKernel( radius );

//blur along rows
QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32 );
GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel );
runRectOperation( image, rowBlur );

//blur along columns
QImage* yBlurImage = new QImage( width, height, QImage::Format_ARGB32 );
GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage, kernel );
runRectOperation( xBlurImage, colBlur );

delete[] kernel;
return yBlurImage;
}

void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
{
int width = block.image->width();
int height = block.image->height();
int sourceBpl = block.image->bytesPerLine();

unsigned char* outputLineRef = mDestImage->scanLine( block.beginLine );
QRgb* destRef = 0;
if ( mDirection == ByRow )
{
unsigned char* sourceFirstLine = block.image->scanLine( 0 );
unsigned char* sourceRef;

//blur along rows
for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
{
sourceRef = sourceFirstLine;
destRef = ( QRgb* )outputLineRef;
for ( int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
{
*destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
}
}
}
else
{
unsigned char* sourceRef = block.image->scanLine( block.beginLine );
for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
{
destRef = ( QRgb* )outputLineRef;
for ( int x = 0; x < width; ++x, ++destRef )
{
*destRef = gaussianBlurHorizontal( x, sourceRef, width );
}
}
}
}

inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical( const int posy, unsigned char *sourceFirstLine, const int sourceBpl, const int height )
{
double r = 0;
double b = 0;
double g = 0;
double a = 0;
int y;
unsigned char *ref;

for ( int i = 0; i <= mRadius*2; ++i )
{
y = qBound( 0, posy + ( i - mRadius ), height - 1 );
ref = sourceFirstLine + sourceBpl * y;

QRgb* refRgb = ( QRgb* )ref;
r += mKernel[i] * qRed( *refRgb );
g += mKernel[i] * qGreen( *refRgb );
b += mKernel[i] * qBlue( *refRgb );
a += mKernel[i] * qAlpha( *refRgb );
}

return qRgba( r, g, b, a );
}

inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal( const int posx, unsigned char *sourceFirstLine, const int width )
{
double r = 0;
double b = 0;
double g = 0;
double a = 0;
int x;
unsigned char *ref;

for ( int i = 0; i <= mRadius*2; ++i )
{
x = qBound( 0, posx + ( i - mRadius ), width - 1 );
ref = sourceFirstLine + x * 4;

QRgb* refRgb = ( QRgb* )ref;
r += mKernel[i] * qRed( *refRgb );
g += mKernel[i] * qGreen( *refRgb );
b += mKernel[i] * qBlue( *refRgb );
a += mKernel[i] * qAlpha( *refRgb );
}

return qRgba( r, g, b, a );
}


double* QgsImageOperation::createGaussianKernel( const int radius )
{
double* kernel = new double[ radius*2+1 ];
double sigma = radius / 3.0;
double twoSigmaSquared = 2 * sigma * sigma;
double coefficient = 1.0 / sqrt( M_PI * twoSigmaSquared );
double expCoefficient = -1.0 / twoSigmaSquared;

double sum = 0;
double result;
for ( int i = 0; i <= radius; ++i )
{
result = coefficient * exp( i * i * expCoefficient );
kernel[ radius - i ] = result;
sum += result;
if ( i > 0 )
{
kernel[radius + i] = result;
sum += result;
}
}
//normalize
for ( int i = 0; i <= radius * 2; ++i )
{
kernel[i] /= sum;
}
return kernel;
}


// flip

Expand Down Expand Up @@ -583,3 +759,7 @@ void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef, const int

delete[] tempLine;
}




45 changes: 45 additions & 0 deletions src/core/effects/qgsimageoperation.h
Expand Up @@ -33,6 +33,9 @@ class QgsVectorColorRampV2;
* to the calling procedure to ensure that any passed images are of the correct
* format.
*
* Operations are written to either modify an image in place or return a new image, depending
* on which is faster for the particular operation.
*
* \note These operations do not work using premultiplied ARGB32_Premultiplied images
* - please make sure the images are converted to standard ARGB32 images prior to calling
* these operations.
Expand Down Expand Up @@ -145,6 +148,14 @@ class CORE_EXPORT QgsImageOperation
*/
static void stackBlur( QImage &image, const int radius, const bool alphaOnly = false );

/**Performs a gaussian blur on an image. Gaussian blur is slower but results in a high
* quality blur.
* @param image QImage to blur
* @param radius blur radius in pixels
* @returns blurred image
*/
static QImage* gaussianBlur( QImage &image, const int radius );

/**Flips an image horizontally or vertically
* @param image QImage to flip
* @param type type of flip to perform (horizontal or vertical)
Expand All @@ -168,6 +179,10 @@ class CORE_EXPORT QgsImageOperation
QImage* image;
};

//for rect operations
template <typename RectOperation> static void runRectOperation( QImage &image, RectOperation& operation );
template <class RectOperation> static void runRectOperationOnWholeImage( QImage &image, RectOperation& operation );

//for per pixel operations
template <class PixelOperation> static void runPixelOperation( QImage &image, PixelOperation& operation );
template <class PixelOperation> static void runPixelOperationOnWholeImage( QImage &image, PixelOperation& operation );
Expand Down Expand Up @@ -377,6 +392,36 @@ class CORE_EXPORT QgsImageOperation
int mi2;
};

static double *createGaussianKernel( const int radius );

class GaussianBlurOperation
{
public:
GaussianBlurOperation( int radius, LineOperationDirection direction, QImage* destImage, double* kernel )
: mRadius( radius )
, mDirection( direction )
, mDestImage( destImage )
, mDestImageBpl( destImage->bytesPerLine() )
, mKernel( kernel )
{}

typedef void result_type;

void operator()( ImageBlock& block );

private:
int mRadius;
LineOperationDirection mDirection;
QImage* mDestImage;
int mDestImageBpl;
double* mKernel;

inline QRgb gaussianBlurVertical( const int posy, unsigned char *sourceFirstLine, const int sourceBpl, const int height );
inline QRgb gaussianBlurHorizontal( const int posx, unsigned char *sourceFirstLine, const int width );
};

//flip


class FlipLineOperation
{
Expand Down
26 changes: 25 additions & 1 deletion tests/src/core/testqgsimageoperation.cpp
Expand Up @@ -68,6 +68,10 @@ class TestQgsImageOperation : public QObject
void stackBlur();
void alphaOnlyBlur();

//gaussian blur
void gaussianBlur();
void gaussianBlurSmall();

//flip
void flipHorizontal();
void flipVertical();
Expand All @@ -78,7 +82,6 @@ class TestQgsImageOperation : public QObject
QString mSampleImage;

bool imageCheck( QString testName , QImage &image, int mismatchCount );

};

void TestQgsImageOperation::initTestCase()
Expand Down Expand Up @@ -335,6 +338,27 @@ void TestQgsImageOperation::alphaOnlyBlur()
QVERIFY( result );
}

void TestQgsImageOperation::gaussianBlur()
{
QImage image( mSampleImage );
QImage* blurredImage = QgsImageOperation::gaussianBlur( image, 30 );

bool result = imageCheck( QString( "imageop_gaussianblur" ), *blurredImage, 0 );
delete blurredImage;
QVERIFY( result );
}

//todo small, zero radius
void TestQgsImageOperation::gaussianBlurSmall()
{
QImage image( QString( TEST_DATA_DIR ) + QDir::separator() + "small_sample_image.png" );
QImage* blurredImage = QgsImageOperation::gaussianBlur( image, 10 );

bool result = imageCheck( QString( "imageop_gaussianblur_small" ), *blurredImage, 0 );
delete blurredImage;
QVERIFY( result );
}

void TestQgsImageOperation::flipHorizontal()
{
QImage image( mSampleImage );
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 670ad7b

Please sign in to comment.