New QgsImageOperation class for operations that modify QImages
Contains framework for multithreaded operations on QImages, and numerous operations such as grayscale, hue/saturation, brightness/ contrast modification, flip, blur, distance transform, alpha modification and color overlays.
@ -81,6 +81,7 @@ INCLUDE_DIRECTORIES(
|
||||
../src/core/pal
|
||||
../src/core/composer
|
||||
../src/core/diagram
|
||||
../src/core/effects
|
||||
../src/core/dxf
|
||||
../src/core/gps
|
||||
../src/core/layertree
|
||||
|
@ -170,6 +170,8 @@
|
||||
%Include diagram/qgspiediagram.sip
|
||||
%Include diagram/qgstextdiagram.sip
|
||||
|
||||
%Include effects/qgsimageoperation.sip
|
||||
|
||||
%Include gps/qgsgpsconnection.sip
|
||||
%Include gps/qgsgpsconnectionregistry.sip
|
||||
%Include gps/qgsgpsdconnection.sip
|
||||
|
117
python/core/effects/qgsimageoperation.sip
Normal file
@ -0,0 +1,117 @@
|
||||
/** \ingroup core
|
||||
* \class QgsImageOperation
|
||||
* \brief Contains operations and filters which apply to QImages
|
||||
*
|
||||
* A set of optimised pixel manipulation operations and filters which can be applied
|
||||
* to QImages.
|
||||
* \note Added in version 2.7
|
||||
*/
|
||||
class QgsImageOperation
|
||||
{
|
||||
%TypeHeaderCode
|
||||
#include <qgsimageoperation.h>
|
||||
%End
|
||||
|
||||
public:
|
||||
|
||||
/** Modes for converting a QImage to grayscale
|
||||
*/
|
||||
enum GrayscaleMode
|
||||
{
|
||||
GrayscaleLightness, /*< keep the lightness of the color, drops the saturation */
|
||||
GrayscaleLuminosity, /*< grayscale by perceptual luminosity (weighted sum of color RGB components) */
|
||||
GrayscaleAverage /*< grayscale by taking average of color RGB components */
|
||||
};
|
||||
|
||||
/** Flip operation types
|
||||
*/
|
||||
enum FlipType
|
||||
{
|
||||
FlipHorizontal, /*< flip the image horizontally */
|
||||
FlipVertical /*< flip the image vertically */
|
||||
};
|
||||
|
||||
/**Convert a QImage to a grayscale image. Alpha channel is preserved.
|
||||
* @param image QImage to convert
|
||||
* @param mode mode to use during grayscale conversion
|
||||
*/
|
||||
static void convertToGrayscale( QImage &image, const GrayscaleMode mode = GrayscaleLuminosity );
|
||||
|
||||
/**Alter the brightness or contrast of a QImage.
|
||||
* @param image QImage to alter
|
||||
* @param brightness brightness value, in the range -255 to 255. A brightness value of 0 indicates
|
||||
* no change to brightness, a negative value will darken the image, and a positive value will brighten
|
||||
* the image.
|
||||
* @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.
|
||||
*/
|
||||
static void adjustBrightnessContrast( QImage &image, const int brightness, const double contrast );
|
||||
|
||||
/**Alter the hue or saturation of a QImage.
|
||||
* @param image QImage to alter
|
||||
* @param saturation double between 0 and 2 inclusive, where 0 = desaturate and 1.0 = no change
|
||||
* @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
|
||||
*/
|
||||
static void adjustHueSaturation( QImage &image, const double saturation, const QColor& colorizeColor = QColor(),
|
||||
const double colorizeStrength = 1.0 );
|
||||
|
||||
/**Multiplies opacity of image pixel values by a factor.
|
||||
* @param image QImage to alter
|
||||
* @param factor factor to multiple pixel's opacity by
|
||||
*/
|
||||
static void multiplyOpacity( QImage &image, const double factor );
|
||||
|
||||
/**Overlays a color onto an image. This operation retains the alpha channel of the
|
||||
* original image, but replaces all image pixel colors with the specified color.
|
||||
* @param image QImage to alter
|
||||
* @param color color to overlay (any alpha component of the color is ignored)
|
||||
*/
|
||||
static void overlayColor( QImage &image, const QColor& color );
|
||||
|
||||
/**Struct for storing properties of a distance transform operation*/
|
||||
struct DistanceTransformProperties
|
||||
{
|
||||
/**Set to true to perform the distance transform on transparent pixels
|
||||
* in the source image, set to false to perform the distance transform
|
||||
* on opaque pixels
|
||||
*/
|
||||
bool shadeExterior;
|
||||
/**Set to true to automatically calculate the maximum distance in the
|
||||
* transform to use as the spread value
|
||||
*/
|
||||
bool useMaxDistance;
|
||||
/**Maximum distance (in pixels) for the distance transform shading to
|
||||
* spread
|
||||
*/
|
||||
double spread;
|
||||
/**Color ramp to use for shading the distance transform
|
||||
*/
|
||||
QgsVectorColorRampV2* ramp;
|
||||
};
|
||||
|
||||
/**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
|
||||
*/
|
||||
static void distanceTransform( QImage &image, const QgsImageOperation::DistanceTransformProperties& properties );
|
||||
|
||||
/**Performs a stack blur on an image. Stack blur represents a good balance between
|
||||
* speed and blur quality.
|
||||
* @param image QImage to blur
|
||||
* @param radius blur radius in pixels, maximum value of 16
|
||||
* @param alphaOnly set to true to blur only the alpha component of the image
|
||||
*/
|
||||
static void stackBlur( QImage &image, const int radius, const bool alphaOnly = false );
|
||||
|
||||
/**Flips an image horizontally or vertically
|
||||
* @param image QImage to flip
|
||||
* @param type type of flip to perform (horizontal or vertical)
|
||||
*/
|
||||
static void flipImage( QImage &image, FlipType type );
|
||||
|
||||
};
|
@ -46,6 +46,8 @@ SET(QGIS_CORE_SRCS
|
||||
diagram/qgspiediagram.cpp
|
||||
diagram/qgstextdiagram.cpp
|
||||
diagram/qgshistogramdiagram.cpp
|
||||
|
||||
effects/qgsimageoperation.cpp
|
||||
|
||||
layertree/qgslayertreegroup.cpp
|
||||
layertree/qgslayertreelayer.cpp
|
||||
@ -554,6 +556,8 @@ SET(QGIS_CORE_HDRS
|
||||
diagram/qgspiediagram.h
|
||||
diagram/qgstextdiagram.h
|
||||
diagram/qgshistogramdiagram.h
|
||||
|
||||
effects/qgsimageoperation.h
|
||||
|
||||
composer/qgsaddremovemultiframecommand.h
|
||||
composer/qgscomposerarrow.h
|
||||
@ -647,6 +651,7 @@ INCLUDE_DIRECTORIES(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
composer
|
||||
dxf
|
||||
effects
|
||||
layertree
|
||||
pal
|
||||
raster
|
||||
|
585
src/core/effects/qgsimageoperation.cpp
Normal file
@ -0,0 +1,585 @@
|
||||
/***************************************************************************
|
||||
qgsimageoperation.cpp
|
||||
----------------------
|
||||
begin : January 2015
|
||||
copyright : (C) 2015 by Nyall Dawson
|
||||
email : nyall.dawson@gmail.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 "qgsimageoperation.h"
|
||||
#include "qgis.h"
|
||||
#include "qgsvectorcolorrampv2.h"
|
||||
#include "qgslogger.h"
|
||||
#include <QtConcurrentMap>
|
||||
#include <QColor>
|
||||
#include <QPainter>
|
||||
#include <qmath.h>
|
||||
|
||||
//determined via trial-and-error. Could possibly be optimised, or varied
|
||||
//depending on the image size.
|
||||
#define BLOCK_THREADS 16
|
||||
|
||||
#define INF 1E20
|
||||
|
||||
template <typename PixelOperation>
|
||||
void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation& operation )
|
||||
{
|
||||
if ( image.height() * image.width() < 100000 )
|
||||
{
|
||||
//small image, don't multithread
|
||||
//this threshold was determined via testing various images
|
||||
runPixelOperationOnWholeImage( image, operation );
|
||||
}
|
||||
else
|
||||
{
|
||||
//large image, multithread operation
|
||||
QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation ) ;
|
||||
runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
|
||||
}
|
||||
}
|
||||
|
||||
template <typename PixelOperation>
|
||||
void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation& operation )
|
||||
{
|
||||
int height = image.height();
|
||||
int width = image.width();
|
||||
for ( int y = 0; y < height; ++y )
|
||||
{
|
||||
QRgb* ref = ( QRgb* )image.scanLine( y );
|
||||
for ( int x = 0; x < width; ++x )
|
||||
{
|
||||
operation( ref[x], x, y );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//linear operations
|
||||
|
||||
template <typename LineOperation>
|
||||
void QgsImageOperation::runLineOperation( QImage &image, LineOperation& 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
|
||||
runLineOperationOnWholeImage( image, operation );
|
||||
}
|
||||
else
|
||||
{
|
||||
//large image, multithread operation
|
||||
QgsImageOperation::ProcessBlockUsingLineOperation<LineOperation> blockOp( operation ) ;
|
||||
runBlockOperationInThreads( image, blockOp, operation.direction() );
|
||||
}
|
||||
}
|
||||
|
||||
template <class LineOperation>
|
||||
void QgsImageOperation::runLineOperationOnWholeImage( QImage &image, LineOperation& operation )
|
||||
{
|
||||
int height = image.height();
|
||||
int width = image.width();
|
||||
|
||||
//do something with whole lines
|
||||
int bpl = image.bytesPerLine();
|
||||
if ( operation.direction() == ByRow )
|
||||
{
|
||||
for ( int y = 0; y < height; ++y )
|
||||
{
|
||||
QRgb* ref = ( QRgb* )image.scanLine( y );
|
||||
operation( ref, width, bpl );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//by column
|
||||
unsigned char* ref = image.scanLine( 0 );
|
||||
for ( int x = 0; x < width; ++x, ref += 4 )
|
||||
{
|
||||
operation(( QRgb* )ref, height, bpl );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//multithreaded block processing
|
||||
|
||||
template <typename BlockOperation>
|
||||
void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction )
|
||||
{
|
||||
QList< ImageBlock > blocks;
|
||||
unsigned int height = image.height();
|
||||
unsigned int width = image.width();
|
||||
|
||||
unsigned int blockDimension1 = ( direction == QgsImageOperation::ByRow ) ? height : width;
|
||||
unsigned int blockDimension2 = ( direction == QgsImageOperation::ByRow ) ? width : height;
|
||||
|
||||
//chunk image up into vertical blocks
|
||||
blocks.reserve( BLOCK_THREADS );
|
||||
unsigned int begin = 0;
|
||||
unsigned int blockLen = blockDimension1 / BLOCK_THREADS;
|
||||
for ( unsigned int block = 0; block < BLOCK_THREADS; ++block, begin += blockLen )
|
||||
{
|
||||
ImageBlock newBlock;
|
||||
newBlock.beginLine = begin;
|
||||
//make sure last block goes to end of image
|
||||
newBlock.endLine = block < ( BLOCK_THREADS - 1 ) ? begin + blockLen : blockDimension1;
|
||||
newBlock.lineLength = blockDimension2;
|
||||
newBlock.image = ℑ
|
||||
blocks << newBlock;
|
||||
}
|
||||
|
||||
//process blocks
|
||||
QtConcurrent::blockingMap( blocks, operation );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//operation specific code
|
||||
//
|
||||
|
||||
//grayscale
|
||||
|
||||
void QgsImageOperation::convertToGrayscale( QImage &image, const GrayscaleMode mode )
|
||||
{
|
||||
GrayscalePixelOperation operation( mode );
|
||||
runPixelOperation( image, operation );
|
||||
}
|
||||
|
||||
void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb, const int x, const int y )
|
||||
{
|
||||
Q_UNUSED( x );
|
||||
Q_UNUSED( y );
|
||||
switch ( mMode )
|
||||
{
|
||||
case GrayscaleLuminosity:
|
||||
grayscaleLuminosityOp( rgb );
|
||||
return;
|
||||
case GrayscaleAverage:
|
||||
grayscaleAverageOp( rgb );
|
||||
return;
|
||||
case GrayscaleLightness:
|
||||
default:
|
||||
grayscaleLightnessOp( rgb );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void QgsImageOperation::grayscaleLightnessOp( QRgb &rgb )
|
||||
{
|
||||
int red = qRed( rgb );
|
||||
int green = qGreen( rgb );
|
||||
int blue = qBlue( rgb );
|
||||
|
||||
int min = qMin( qMin( red, green ), blue );
|
||||
int max = qMax( qMax( red, green ), blue );
|
||||
|
||||
int lightness = qMin(( min + max ) / 2, 255 );
|
||||
rgb = qRgba( lightness, lightness, lightness, qAlpha( rgb ) );
|
||||
}
|
||||
|
||||
void QgsImageOperation::grayscaleLuminosityOp( QRgb &rgb )
|
||||
{
|
||||
int luminosity = 0.21 * qRed( rgb ) + 0.72 * qGreen( rgb ) + 0.07 * qBlue( rgb );
|
||||
rgb = qRgba( luminosity, luminosity, luminosity, qAlpha( rgb ) );
|
||||
}
|
||||
|
||||
void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )
|
||||
{
|
||||
int average = ( qRed( rgb ) + qGreen( rgb ) + qBlue( rgb ) ) / 3;
|
||||
rgb = qRgba( average, average, average, qAlpha( rgb ) );
|
||||
}
|
||||
|
||||
|
||||
//brightness/contrast
|
||||
|
||||
void QgsImageOperation::adjustBrightnessContrast( QImage &image, const int brightness, const double contrast )
|
||||
{
|
||||
BrightnessContrastPixelOperation operation( brightness, contrast );
|
||||
runPixelOperation( image, operation );
|
||||
}
|
||||
|
||||
void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb, const int x, const int y )
|
||||
{
|
||||
Q_UNUSED( x );
|
||||
Q_UNUSED( y );
|
||||
int red = adjustColorComponent( qRed( rgb ), mBrightness, mContrast );
|
||||
int blue = adjustColorComponent( qBlue( rgb ), mBrightness, mContrast );
|
||||
int green = adjustColorComponent( qGreen( rgb ), mBrightness, mContrast );
|
||||
rgb = qRgba( red, green, blue, qAlpha( rgb ) );
|
||||
}
|
||||
|
||||
int QgsImageOperation::adjustColorComponent( int colorComponent, int brightness, double contrastFactor )
|
||||
{
|
||||
return qBound( 0, ( int )(((((( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 255 );
|
||||
}
|
||||
|
||||
//hue/saturation
|
||||
|
||||
void QgsImageOperation::adjustHueSaturation( QImage &image, const double saturation, const QColor &colorizeColor, const double colorizeStrength )
|
||||
{
|
||||
HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0,
|
||||
colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
|
||||
runPixelOperation( image, operation );
|
||||
}
|
||||
|
||||
void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb, const int x, const int y )
|
||||
{
|
||||
Q_UNUSED( x );
|
||||
Q_UNUSED( y );
|
||||
QColor tmpColor( rgb );
|
||||
int h, s, l;
|
||||
tmpColor.getHsl( &h, &s, &l );
|
||||
|
||||
if ( mSaturation < 1.0 )
|
||||
{
|
||||
// Lowering the saturation. Use a simple linear relationship
|
||||
s = qMin(( int )( s * mSaturation ), 255 );
|
||||
}
|
||||
else if ( mSaturation > 1.0 )
|
||||
{
|
||||
// Raising the saturation. Use a saturation curve to prevent
|
||||
// clipping at maximum saturation with ugly results.
|
||||
s = qMin(( int )( 255. * ( 1 - qPow( 1 - ( s / 255. ), qPow( mSaturation, 2 ) ) ) ), 255 );
|
||||
}
|
||||
|
||||
if ( mColorize )
|
||||
{
|
||||
h = mColorizeHue;
|
||||
s = mColorizeSaturation;
|
||||
if ( mColorizeStrength < 1.0 )
|
||||
{
|
||||
//get rgb for colorized color
|
||||
QColor colorizedColor = QColor::fromHsl( h, s, l );
|
||||
int colorizedR, colorizedG, colorizedB;
|
||||
colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
|
||||
|
||||
// Now, linearly scale by colorize strength
|
||||
int r = mColorizeStrength * colorizedR + ( 1 - mColorizeStrength ) * tmpColor.red();
|
||||
int g = mColorizeStrength * colorizedG + ( 1 - mColorizeStrength ) * tmpColor.green();
|
||||
int b = mColorizeStrength * colorizedB + ( 1 - mColorizeStrength ) * tmpColor.blue();
|
||||
|
||||
rgb = qRgba( r, g, b, qAlpha( rgb ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tmpColor.setHsl( h, s, l, qAlpha( rgb ) );
|
||||
rgb = tmpColor.rgba();
|
||||
}
|
||||
|
||||
//multiply opacity
|
||||
|
||||
void QgsImageOperation::multiplyOpacity( QImage &image, const double factor )
|
||||
{
|
||||
if ( qgsDoubleNear( factor, 1.0 ) )
|
||||
{
|
||||
//no change
|
||||
return;
|
||||
}
|
||||
else if ( factor < 1.0 )
|
||||
{
|
||||
//decreasing opacity - we can use the faster DestinationIn composition mode
|
||||
//to reduce the alpha channel
|
||||
QColor transparentFillColor = QColor( 0, 0, 0, 255 * factor );
|
||||
QPainter painter( &image );
|
||||
painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
|
||||
painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
|
||||
painter.end();
|
||||
}
|
||||
else
|
||||
{
|
||||
//increasing opacity - run this as a pixel operation for multithreading
|
||||
MultiplyOpacityPixelOperation operation( factor );
|
||||
runPixelOperation( image, operation );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb, const int x, const int y )
|
||||
{
|
||||
Q_UNUSED( x );
|
||||
Q_UNUSED( y );
|
||||
rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), qBound( 0, qRound( mFactor * qAlpha( rgb ) ), 255 ) );
|
||||
}
|
||||
|
||||
// overlay color
|
||||
|
||||
void QgsImageOperation::overlayColor( QImage &image, const QColor &color )
|
||||
{
|
||||
QColor opaqueColor = color;
|
||||
opaqueColor.setAlpha( 255 );
|
||||
|
||||
//use QPainter SourceIn composition mode to overlay color (fast)
|
||||
//this retains image's alpha channel but replaces color
|
||||
QPainter painter( &image );
|
||||
painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
|
||||
painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
|
||||
painter.end();
|
||||
}
|
||||
|
||||
// distance transform
|
||||
|
||||
void QgsImageOperation::distanceTransform( QImage &image, const DistanceTransformProperties& properties )
|
||||
{
|
||||
if ( ! properties.ramp )
|
||||
{
|
||||
QgsDebugMsg( QString( "no color ramp specified for distance transform" ) );
|
||||
return;
|
||||
}
|
||||
|
||||
//first convert to 1 bit alpha mask array
|
||||
double * array = new double[ image.width() * image.height()];
|
||||
ConvertToArrayPixelOperation convertToArray( image.width(), array, properties.shadeExterior );
|
||||
runPixelOperation( image, convertToArray );
|
||||
|
||||
//calculate distance transform (single threaded only)
|
||||
distanceTransform2d( array, image.width(), image.height() );
|
||||
|
||||
double spread;
|
||||
if ( properties.useMaxDistance )
|
||||
{
|
||||
spread = sqrt( maxValueInDistanceTransformArray( array, image.width() * image.height() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
spread = properties.spread;
|
||||
}
|
||||
|
||||
//shade distance transform
|
||||
ShadeFromArrayOperation shadeFromArray( image.width(), array, spread, properties );
|
||||
runPixelOperation( image, shadeFromArray );
|
||||
delete [] array;
|
||||
}
|
||||
|
||||
void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb, const int x, const int y )
|
||||
{
|
||||
|
||||
// TODO - try initial distance = 1-alphaF, INF for alphaF = 0 only
|
||||
|
||||
int idx = y * mWidth + x;
|
||||
if (( mExterior && qAlpha( rgb ) >= mAlphaThreshold ) || ( !mExterior && qAlpha( rgb ) < mAlphaThreshold ) )
|
||||
{
|
||||
//opaque pixel, so zero distance
|
||||
mArray[ idx ] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//transparent pixel, so initially set distance as infinite
|
||||
mArray[ idx ] = INF;
|
||||
}
|
||||
}
|
||||
|
||||
//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
|
||||
|
||||
/* distance transform of a 1d function using squared distance */
|
||||
void QgsImageOperation::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
|
||||
{
|
||||
int k = 0;
|
||||
v[0] = 0;
|
||||
z[0] = -INF;
|
||||
z[1] = + INF;
|
||||
for ( int q = 1; q <= n - 1; q++ )
|
||||
{
|
||||
double s = (( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
|
||||
while ( s <= z[k] )
|
||||
{
|
||||
k--;
|
||||
s = (( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
|
||||
}
|
||||
k++;
|
||||
v[k] = q;
|
||||
z[k] = s;
|
||||
z[k+1] = + INF;
|
||||
}
|
||||
|
||||
k = 0;
|
||||
for ( int q = 0; q <= n - 1; q++ )
|
||||
{
|
||||
while ( z[k+1] < q )
|
||||
k++;
|
||||
d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
|
||||
}
|
||||
}
|
||||
|
||||
double QgsImageOperation::maxValueInDistanceTransformArray( const double *array, const unsigned int size )
|
||||
{
|
||||
double dtMaxValue = array[0];
|
||||
for ( unsigned int i = 1; i < size; ++i )
|
||||
{
|
||||
if ( array[i] > dtMaxValue )
|
||||
{
|
||||
dtMaxValue = array[i];
|
||||
}
|
||||
}
|
||||
return dtMaxValue;
|
||||
}
|
||||
|
||||
/* distance transform of 2d function using squared distance */
|
||||
void QgsImageOperation::distanceTransform2d( double * im, int width, int height )
|
||||
{
|
||||
int maxDimension = qMax( width, height );
|
||||
|
||||
double *f = new double[ maxDimension ];
|
||||
int *v = new int[ maxDimension ];
|
||||
double *z = new double[ maxDimension + 1 ];
|
||||
double *d = new double[ maxDimension ];
|
||||
|
||||
// transform along columns
|
||||
for ( int x = 0; x < width; x++ )
|
||||
{
|
||||
for ( int y = 0; y < height; y++ )
|
||||
{
|
||||
f[y] = im[ x + y * width ];
|
||||
}
|
||||
distanceTransform1d( f, height, v, z, d );
|
||||
for ( int y = 0; y < height; y++ )
|
||||
{
|
||||
im[ x + y * width ] = d[y];
|
||||
}
|
||||
}
|
||||
|
||||
// transform along rows
|
||||
for ( int y = 0; y < height; y++ )
|
||||
{
|
||||
for ( int x = 0; x < width; x++ )
|
||||
{
|
||||
f[x] = im[ x + y*width ];
|
||||
}
|
||||
distanceTransform1d( f, width, v, z, d );
|
||||
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 )
|
||||
{
|
||||
if ( ! mProperties.ramp )
|
||||
return;
|
||||
|
||||
if ( mSpread == 0 )
|
||||
{
|
||||
rgb = mProperties.ramp->color( 1.0 ).rgba();
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = y * mWidth + x;
|
||||
|
||||
//values are distance squared
|
||||
double squaredVal = mArray[ idx ];
|
||||
if ( squaredVal > mSpreadSquared )
|
||||
{
|
||||
rgb = Qt::transparent;
|
||||
return;
|
||||
}
|
||||
double val = squaredVal > 0 ? qMin(( sqrt( squaredVal ) / mSpread ), 1.0 ) : 0;
|
||||
|
||||
rgb = mProperties.ramp->color( val ).rgba();
|
||||
}
|
||||
|
||||
//stack blur
|
||||
|
||||
void QgsImageOperation::stackBlur( QImage &image, const int radius, const bool alphaOnly )
|
||||
{
|
||||
// culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
|
||||
int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
|
||||
int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius-1];
|
||||
|
||||
int i1 = 0;
|
||||
int i2 = 3;
|
||||
|
||||
if ( alphaOnly ) // this seems to only work right for a black color
|
||||
i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
|
||||
|
||||
StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn, true, i1, i2 );
|
||||
runLineOperation( image, topToBottomBlur );
|
||||
|
||||
StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow, true, i1, i2 );
|
||||
runLineOperation( image, leftToRightBlur );
|
||||
|
||||
StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn, false, i1, i2 );
|
||||
runLineOperation( image, bottomToTopBlur );
|
||||
|
||||
StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow, false, i1, i2 );
|
||||
runLineOperation( image, rightToLeftBlur );
|
||||
}
|
||||
|
||||
void QgsImageOperation::StackBlurLineOperation::operator()( QRgb* startRef, const int lineLength, const int bytesPerLine )
|
||||
{
|
||||
unsigned char* p = ( unsigned char* )startRef;
|
||||
int rgba[4];
|
||||
int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
|
||||
if ( !mForwardDirection )
|
||||
{
|
||||
p += ( lineLength - 1 ) * increment;
|
||||
increment = -increment;
|
||||
}
|
||||
|
||||
for ( int i = mi1; i <= mi2; ++i )
|
||||
{
|
||||
rgba[i] = p[i] << 4;
|
||||
}
|
||||
|
||||
p += increment;
|
||||
for ( int j = 1; j < lineLength; ++j, p += increment )
|
||||
{
|
||||
for ( int i = mi1; i <= mi2; ++i )
|
||||
{
|
||||
p[i] = ( rgba[i] += (( p[i] << 4 ) - rgba[i] ) * mAlpha / 16 ) >> 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// flip
|
||||
|
||||
void QgsImageOperation::flipImage( QImage &image, QgsImageOperation::FlipType type )
|
||||
{
|
||||
FlipLineOperation flipOperation( type == QgsImageOperation::FlipHorizontal ? QgsImageOperation::ByRow : QgsImageOperation::ByColumn );
|
||||
runLineOperation( image, flipOperation );
|
||||
}
|
||||
|
||||
void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef, const int lineLength, const int bytesPerLine )
|
||||
{
|
||||
int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
|
||||
|
||||
//store temporary line
|
||||
unsigned char* p = ( unsigned char* )startRef;
|
||||
unsigned char* tempLine = new unsigned char[ lineLength * 4 ];
|
||||
for ( int i = 0; i < lineLength * 4; ++i, p += increment )
|
||||
{
|
||||
tempLine[i++] = *( p++ );
|
||||
tempLine[i++] = *( p++ );
|
||||
tempLine[i++] = *( p++ );
|
||||
tempLine[i] = *( p );
|
||||
p -= 3;
|
||||
}
|
||||
|
||||
//write values back in reverse order
|
||||
p = ( unsigned char* )startRef;
|
||||
for ( int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
|
||||
{
|
||||
*( p++ ) = tempLine[i++];
|
||||
*( p++ ) = tempLine[i++];
|
||||
*( p++ ) = tempLine[i++];
|
||||
*( p ) = tempLine[i];
|
||||
p -= 3;
|
||||
}
|
||||
|
||||
delete[] tempLine;
|
||||
}
|
402
src/core/effects/qgsimageoperation.h
Normal file
@ -0,0 +1,402 @@
|
||||
/***************************************************************************
|
||||
qgsimageoperation.h
|
||||
--------------------
|
||||
begin : January 2015
|
||||
copyright : (C) 2015 by Nyall Dawson
|
||||
email : nyall.dawson@gmail.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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSIMAGEOPERATION_H
|
||||
#define QGSIMAGEOPERATION_H
|
||||
|
||||
#include <QImage>
|
||||
#include <QColor>
|
||||
#include <QtCore/qmath.h>
|
||||
|
||||
class QgsVectorColorRampV2;
|
||||
|
||||
/** \ingroup core
|
||||
* \class QgsImageOperation
|
||||
* \brief Contains operations and filters which apply to QImages
|
||||
*
|
||||
* A set of optimised pixel manipulation operations and filters which can be applied
|
||||
* 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.
|
||||
*
|
||||
* \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 CORE_EXPORT QgsImageOperation
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
/** Modes for converting a QImage to grayscale
|
||||
*/
|
||||
enum GrayscaleMode
|
||||
{
|
||||
GrayscaleLightness, /*< keep the lightness of the color, drops the saturation */
|
||||
GrayscaleLuminosity, /*< grayscale by perceptual luminosity (weighted sum of color RGB components) */
|
||||
GrayscaleAverage /*< grayscale by taking average of color RGB components */
|
||||
};
|
||||
|
||||
/** Flip operation types
|
||||
*/
|
||||
enum FlipType
|
||||
{
|
||||
FlipHorizontal, /*< flip the image horizontally */
|
||||
FlipVertical /*< flip the image vertically */
|
||||
};
|
||||
|
||||
/**Convert a QImage to a grayscale image. Alpha channel is preserved.
|
||||
* @param image QImage to convert
|
||||
* @param mode mode to use during grayscale conversion
|
||||
*/
|
||||
static void convertToGrayscale( QImage &image, const GrayscaleMode mode = GrayscaleLuminosity );
|
||||
|
||||
/**Alter the brightness or contrast of a QImage.
|
||||
* @param image QImage to alter
|
||||
* @param brightness brightness value, in the range -255 to 255. A brightness value of 0 indicates
|
||||
* no change to brightness, a negative value will darken the image, and a positive value will brighten
|
||||
* the image.
|
||||
* @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.
|
||||
*/
|
||||
static void adjustBrightnessContrast( QImage &image, const int brightness, const double contrast );
|
||||
|
||||
/**Alter the hue or saturation of a QImage.
|
||||
* @param image QImage to alter
|
||||
* @param saturation double between 0 and 2 inclusive, where 0 = desaturate and 1.0 = no change
|
||||
* @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
|
||||
*/
|
||||
static void adjustHueSaturation( QImage &image, const double saturation, const QColor& colorizeColor = QColor(),
|
||||
const double colorizeStrength = 1.0 );
|
||||
|
||||
/**Multiplies opacity of image pixel values by a factor.
|
||||
* @param image QImage to alter
|
||||
* @param factor factor to multiple pixel's opacity by
|
||||
*/
|
||||
static void multiplyOpacity( QImage &image, const double factor );
|
||||
|
||||
/**Overlays a color onto an image. This operation retains the alpha channel of the
|
||||
* original image, but replaces all image pixel colors with the specified color.
|
||||
* @param image QImage to alter
|
||||
* @param color color to overlay (any alpha component of the color is ignored)
|
||||
*/
|
||||
static void overlayColor( QImage &image, const QColor& color );
|
||||
|
||||
/**Struct for storing properties of a distance transform operation*/
|
||||
struct DistanceTransformProperties
|
||||
{
|
||||
DistanceTransformProperties()
|
||||
: shadeExterior( true )
|
||||
, useMaxDistance( true )
|
||||
, spread( 10.0 )
|
||||
, ramp( NULL )
|
||||
{ }
|
||||
|
||||
/**Set to true to perform the distance transform on transparent pixels
|
||||
* in the source image, set to false to perform the distance transform
|
||||
* on opaque pixels
|
||||
*/
|
||||
bool shadeExterior;
|
||||
/**Set to true to automatically calculate the maximum distance in the
|
||||
* transform to use as the spread value
|
||||
*/
|
||||
bool useMaxDistance;
|
||||
/**Maximum distance (in pixels) for the distance transform shading to
|
||||
* spread
|
||||
*/
|
||||
double spread;
|
||||
/**Color ramp to use for shading the distance transform
|
||||
*/
|
||||
QgsVectorColorRampV2* ramp;
|
||||
};
|
||||
|
||||
/**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
|
||||
*/
|
||||
static void distanceTransform( QImage &image, const DistanceTransformProperties& properties );
|
||||
|
||||
/**Performs a stack blur on an image. Stack blur represents a good balance between
|
||||
* speed and blur quality.
|
||||
* @param image QImage to blur
|
||||
* @param radius blur radius in pixels, maximum value of 16
|
||||
* @param alphaOnly set to true to blur only the alpha component of the image
|
||||
*/
|
||||
static void stackBlur( QImage &image, const int radius, const bool alphaOnly = false );
|
||||
|
||||
/**Flips an image horizontally or vertically
|
||||
* @param image QImage to flip
|
||||
* @param type type of flip to perform (horizontal or vertical)
|
||||
*/
|
||||
static void flipImage( QImage &image, FlipType type );
|
||||
|
||||
private:
|
||||
|
||||
//for blocked operations
|
||||
enum LineOperationDirection
|
||||
{
|
||||
ByRow,
|
||||
ByColumn
|
||||
};
|
||||
template <class BlockOperation> static void runBlockOperationInThreads( QImage &image, BlockOperation& operation, LineOperationDirection direction );
|
||||
struct ImageBlock
|
||||
{
|
||||
unsigned int beginLine;
|
||||
unsigned int endLine;
|
||||
unsigned int lineLength;
|
||||
QImage* image;
|
||||
};
|
||||
|
||||
//for per pixel operations
|
||||
template <class PixelOperation> static void runPixelOperation( QImage &image, PixelOperation& operation );
|
||||
template <class PixelOperation> static void runPixelOperationOnWholeImage( QImage &image, PixelOperation& operation );
|
||||
template <class PixelOperation>
|
||||
struct ProcessBlockUsingPixelOperation
|
||||
{
|
||||
ProcessBlockUsingPixelOperation( PixelOperation& operation )
|
||||
: mOperation( operation ) { }
|
||||
|
||||
typedef void result_type;
|
||||
|
||||
void operator()( ImageBlock& block )
|
||||
{
|
||||
for ( unsigned int y = block.beginLine; y < block.endLine; ++y )
|
||||
{
|
||||
QRgb* ref = ( QRgb* )block.image->scanLine( y );
|
||||
for ( unsigned int x = 0; x < block.lineLength; ++x )
|
||||
{
|
||||
mOperation( ref[x], x, y );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PixelOperation& mOperation;
|
||||
};
|
||||
|
||||
//for linear operations
|
||||
template <typename LineOperation> static void runLineOperation( QImage &image, LineOperation& operation );
|
||||
template <class LineOperation> static void runLineOperationOnWholeImage( QImage &image, LineOperation& operation );
|
||||
template <class LineOperation>
|
||||
struct ProcessBlockUsingLineOperation
|
||||
{
|
||||
ProcessBlockUsingLineOperation( LineOperation& operation )
|
||||
: mOperation( operation ) { }
|
||||
|
||||
typedef void result_type;
|
||||
|
||||
void operator()( ImageBlock& block )
|
||||
{
|
||||
//do something with whole lines
|
||||
int bpl = block.image->bytesPerLine();
|
||||
if ( mOperation.direction() == ByRow )
|
||||
{
|
||||
for ( unsigned int y = block.beginLine; y < block.endLine; ++y )
|
||||
{
|
||||
QRgb* ref = ( QRgb* )block.image->scanLine( y );
|
||||
mOperation( ref, block.lineLength, bpl );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//by column
|
||||
unsigned char* ref = block.image->scanLine( 0 ) + 4 * block.beginLine;
|
||||
for ( unsigned int x = block.beginLine; x < block.endLine; ++x, ref += 4 )
|
||||
{
|
||||
mOperation(( QRgb* )ref, block.lineLength, bpl );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LineOperation& mOperation;
|
||||
};
|
||||
|
||||
|
||||
//individual operation implementations
|
||||
|
||||
class GrayscalePixelOperation
|
||||
{
|
||||
public:
|
||||
GrayscalePixelOperation( const GrayscaleMode mode )
|
||||
: mMode( mode )
|
||||
{ }
|
||||
|
||||
void operator()( QRgb& rgb, const int x, const int y );
|
||||
|
||||
private:
|
||||
GrayscaleMode mMode;
|
||||
};
|
||||
static void grayscaleLightnessOp( QRgb& rgb );
|
||||
static void grayscaleLuminosityOp( QRgb& rgb );
|
||||
static void grayscaleAverageOp( QRgb& rgb );
|
||||
|
||||
|
||||
class BrightnessContrastPixelOperation
|
||||
{
|
||||
public:
|
||||
BrightnessContrastPixelOperation( const int brightness, const double contrast )
|
||||
: mBrightness( brightness )
|
||||
, mContrast( contrast )
|
||||
{ }
|
||||
|
||||
void operator()( QRgb& rgb, const int x, const int y );
|
||||
|
||||
private:
|
||||
int mBrightness;
|
||||
double mContrast;
|
||||
};
|
||||
|
||||
|
||||
class HueSaturationPixelOperation
|
||||
{
|
||||
public:
|
||||
HueSaturationPixelOperation( const double saturation, const bool colorize,
|
||||
const int colorizeHue, const int colorizeSaturation,
|
||||
const double colorizeStrength )
|
||||
: mSaturation( saturation )
|
||||
, mColorize( colorize )
|
||||
, mColorizeHue( colorizeHue )
|
||||
, mColorizeSaturation( colorizeSaturation )
|
||||
, mColorizeStrength( colorizeStrength )
|
||||
{ }
|
||||
|
||||
void operator()( QRgb& rgb, const int x, const int y );
|
||||
|
||||
private:
|
||||
double mSaturation; // [0, 2], 1 = no change
|
||||
bool mColorize;
|
||||
int mColorizeHue;
|
||||
int mColorizeSaturation;
|
||||
double mColorizeStrength; // [0,1]
|
||||
};
|
||||
static int adjustColorComponent( int colorComponent, int brightness, double contrastFactor );
|
||||
|
||||
|
||||
class MultiplyOpacityPixelOperation
|
||||
{
|
||||
public:
|
||||
MultiplyOpacityPixelOperation( const double factor )
|
||||
: mFactor( factor )
|
||||
{ }
|
||||
|
||||
void operator()( QRgb& rgb, const int x, const int y );
|
||||
|
||||
private:
|
||||
double mFactor;
|
||||
};
|
||||
|
||||
class ConvertToArrayPixelOperation
|
||||
{
|
||||
public:
|
||||
ConvertToArrayPixelOperation( const int width, double * array, const bool exterior = true, const int alphaThreshold = 255 )
|
||||
: mWidth( width )
|
||||
, mArray( array )
|
||||
, mExterior( exterior )
|
||||
, mAlphaThreshold( alphaThreshold )
|
||||
{
|
||||
}
|
||||
|
||||
void operator()( QRgb& rgb, const int x, const int y );
|
||||
|
||||
private:
|
||||
int mWidth;
|
||||
double * mArray;
|
||||
bool mExterior;
|
||||
int mAlphaThreshold;
|
||||
};
|
||||
|
||||
class ShadeFromArrayOperation
|
||||
{
|
||||
public:
|
||||
ShadeFromArrayOperation( const int width, double* array, const double spread,
|
||||
const DistanceTransformProperties& properties )
|
||||
: mWidth( width )
|
||||
, mArray( array )
|
||||
, mSpread( spread )
|
||||
, mProperties( properties )
|
||||
{
|
||||
mSpreadSquared = qPow( mSpread, 2.0 );
|
||||
}
|
||||
|
||||
void operator()( QRgb& rgb, const int x, const int y );
|
||||
|
||||
private:
|
||||
int mWidth;
|
||||
double * mArray;
|
||||
double mSpread;
|
||||
double mSpreadSquared;
|
||||
const DistanceTransformProperties& mProperties;
|
||||
};
|
||||
static void distanceTransform2d( double *im, int width, int height );
|
||||
static void distanceTransform1d( double *f, int n, int *v, double *z, double *d );
|
||||
static double maxValueInDistanceTransformArray( const double *array, const unsigned int size );
|
||||
|
||||
|
||||
class StackBlurLineOperation
|
||||
{
|
||||
public:
|
||||
StackBlurLineOperation( int alpha, LineOperationDirection direction, bool forwardDirection, int i1, int i2 )
|
||||
: mAlpha( alpha )
|
||||
, mDirection( direction )
|
||||
, mForwardDirection( forwardDirection )
|
||||
, mi1( i1 )
|
||||
, mi2( i2 )
|
||||
{ }
|
||||
|
||||
typedef void result_type;
|
||||
|
||||
LineOperationDirection direction() { return mDirection; }
|
||||
|
||||
void operator()( QRgb* startRef, const int lineLength, const int bytesPerLine );
|
||||
|
||||
private:
|
||||
int mAlpha;
|
||||
LineOperationDirection mDirection;
|
||||
bool mForwardDirection;
|
||||
int mi1;
|
||||
int mi2;
|
||||
};
|
||||
|
||||
|
||||
class FlipLineOperation
|
||||
{
|
||||
public:
|
||||
FlipLineOperation( LineOperationDirection direction )
|
||||
: mDirection( direction )
|
||||
{ }
|
||||
|
||||
typedef void result_type;
|
||||
|
||||
LineOperationDirection direction() { return mDirection; }
|
||||
|
||||
void operator()( QRgb* startRef, const int lineLength, const int bytesPerLine );
|
||||
|
||||
private:
|
||||
LineOperationDirection mDirection;
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSIMAGEOPERATION_H
|
||||
|
@ -9,6 +9,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_SOURCE_DIR}/src/core
|
||||
${CMAKE_SOURCE_DIR}/src/core/composer
|
||||
${CMAKE_SOURCE_DIR}/src/core/effects
|
||||
${CMAKE_SOURCE_DIR}/src/core/layertree
|
||||
${CMAKE_SOURCE_DIR}/src/core/raster
|
||||
${CMAKE_SOURCE_DIR}/src/core/symbology-ng
|
||||
@ -145,3 +146,4 @@ ADD_QGIS_TEST(vectorlayerjoinbuffer testqgsvectorlayerjoinbuffer.cpp )
|
||||
ADD_QGIS_TEST(maplayerstylemanager testqgsmaplayerstylemanager.cpp )
|
||||
ADD_QGIS_TEST(pointlocatortest testqgspointlocator.cpp )
|
||||
ADD_QGIS_TEST(snappingutilstest testqgssnappingutils.cpp )
|
||||
ADD_QGIS_TEST(imageoperationtest testqgsimageoperation.cpp)
|
||||
|
383
tests/src/core/testqgsimageoperation.cpp
Normal file
@ -0,0 +1,383 @@
|
||||
/***************************************************************************
|
||||
testqgsimageoperations.cpp
|
||||
--------------------------
|
||||
begin : January 2015
|
||||
copyright : (C) 2015 by Nyall Dawson
|
||||
email : nyall dot dawson 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 "qgsimageoperation.h"
|
||||
#include "qgsvectorcolorrampv2.h"
|
||||
#include <QObject>
|
||||
#include <QtTest/QtTest>
|
||||
#include "qgsrenderchecker.h"
|
||||
|
||||
class TestQgsImageOperation : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();// will be called before the first testfunction is executed.
|
||||
void cleanupTestCase();// will be called after the last testfunction was executed.
|
||||
void init();// will be called before each testfunction is executed.
|
||||
void cleanup();// will be called after every testfunction.
|
||||
void smallImageOp(); //test operation on small image (single threaded op)
|
||||
|
||||
//grayscale
|
||||
void grayscaleLightness();
|
||||
void grayscaleLuminosity();
|
||||
void grayscaleAverage();
|
||||
|
||||
//brightness/contrast
|
||||
void brightnessContrastNoChange();
|
||||
void increaseBrightness();
|
||||
void decreaseBrightness();
|
||||
void increaseContrast();
|
||||
void decreaseContrast();
|
||||
|
||||
//hue/saturation
|
||||
void hueSaturationNoChange();
|
||||
void increaseSaturation();
|
||||
void decreaseSaturation();
|
||||
void colorizeFull();
|
||||
void colorizePartial();
|
||||
|
||||
//multiply opacity
|
||||
void opacityNoChange();
|
||||
void opacityIncrease();
|
||||
void opacityDecrease();
|
||||
|
||||
//overlay color
|
||||
void overlayColor();
|
||||
|
||||
//distance transform
|
||||
void distanceTransformMaxDist();
|
||||
void distanceTransformSetSpread();
|
||||
void distanceTransformInterior();
|
||||
|
||||
//stack blur
|
||||
void stackBlur();
|
||||
void alphaOnlyBlur();
|
||||
|
||||
//flip
|
||||
void flipHorizontal();
|
||||
void flipVertical();
|
||||
|
||||
private:
|
||||
|
||||
QString mReport;
|
||||
QString mSampleImage;
|
||||
|
||||
bool imageCheck( QString testName , QImage &image, int mismatchCount );
|
||||
|
||||
};
|
||||
|
||||
void TestQgsImageOperation::initTestCase()
|
||||
{
|
||||
mReport += "<h1>Image Operation Tests</h1>\n";
|
||||
mSampleImage = QString( TEST_DATA_DIR ) + QDir::separator() + "sample_image.png";
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::cleanupTestCase()
|
||||
{
|
||||
QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html";
|
||||
QFile myFile( myReportFile );
|
||||
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
|
||||
{
|
||||
QTextStream myQTextStream( &myFile );
|
||||
myQTextStream << mReport;
|
||||
myFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::cleanup()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::smallImageOp()
|
||||
{
|
||||
QImage image( QString( TEST_DATA_DIR ) + QDir::separator() + "small_sample_image.png" );
|
||||
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleLightness );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_smallimage" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::grayscaleLightness()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleLightness );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_graylightness" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::grayscaleLuminosity()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleLuminosity );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_grayluminosity" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::grayscaleAverage()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleAverage );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_grayaverage" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::brightnessContrastNoChange()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustBrightnessContrast( image, 0, 1.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_bcnochange" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::increaseBrightness()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustBrightnessContrast( image, 50, 1.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_increasebright" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::decreaseBrightness()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustBrightnessContrast( image, -50, 1.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_decreasebright" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::increaseContrast()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustBrightnessContrast( image, 0, 30.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_increasecontrast" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::decreaseContrast()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustBrightnessContrast( image, 0, 0.1 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_decreasecontrast" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::hueSaturationNoChange()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustHueSaturation( image, 1.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_satnochange" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::increaseSaturation()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustHueSaturation( image, 5.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_increasesat" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::decreaseSaturation()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustHueSaturation( image, 0.5 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_decreasesat" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::colorizeFull()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustHueSaturation( image, 1.0, QColor( 255, 255, 0 ), 1.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_colorizefull" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::colorizePartial()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::adjustHueSaturation( image, 1.0, QColor( 255, 255, 0 ), 0.5 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_colorizepartial" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::opacityNoChange()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::multiplyOpacity( image, 1.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_opacitynochange" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::opacityIncrease()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::multiplyOpacity( image, 2.0 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_opacityincrease" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::opacityDecrease()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::multiplyOpacity( image, 0.5 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_opacitydecrease" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::overlayColor()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::overlayColor( image, QColor( 0, 255, 255 ) );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_overlaycolor" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::distanceTransformMaxDist()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsVectorGradientColorRampV2 ramp;
|
||||
QgsImageOperation::DistanceTransformProperties props;
|
||||
props.useMaxDistance = true;
|
||||
props.ramp = &ramp;
|
||||
props.shadeExterior = true;
|
||||
|
||||
QgsImageOperation::distanceTransform( image, props );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_dt_max" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::distanceTransformSetSpread()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsVectorGradientColorRampV2 ramp;
|
||||
QgsImageOperation::DistanceTransformProperties props;
|
||||
props.useMaxDistance = false;
|
||||
props.spread = 10;
|
||||
props.ramp = &ramp;
|
||||
props.shadeExterior = true;
|
||||
|
||||
QgsImageOperation::distanceTransform( image, props );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_dt_spread" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::distanceTransformInterior()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsVectorGradientColorRampV2 ramp;
|
||||
QgsImageOperation::DistanceTransformProperties props;
|
||||
props.useMaxDistance = true;
|
||||
props.ramp = &ramp;
|
||||
props.shadeExterior = false;
|
||||
|
||||
QgsImageOperation::distanceTransform( image, props );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_dt_interior" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::stackBlur()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::stackBlur( image, 10 );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_stackblur" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::alphaOnlyBlur()
|
||||
{
|
||||
QImage image( QString( TEST_DATA_DIR ) + QDir::separator() + "small_sample_image.png" );
|
||||
QgsImageOperation::stackBlur( image, 10, true );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_stackblur_alphaonly" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::flipHorizontal()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::flipImage( image, QgsImageOperation::FlipHorizontal );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_fliphoz" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
void TestQgsImageOperation::flipVertical()
|
||||
{
|
||||
QImage image( mSampleImage );
|
||||
QgsImageOperation::flipImage( image, QgsImageOperation::FlipVertical );
|
||||
|
||||
bool result = imageCheck( QString( "imageop_flipvert" ), image, 0 );
|
||||
QVERIFY( result );
|
||||
}
|
||||
|
||||
//
|
||||
// Private helper functions not called directly by CTest
|
||||
//
|
||||
|
||||
bool TestQgsImageOperation::imageCheck( QString testName, QImage &image, int mismatchCount )
|
||||
{
|
||||
//draw background
|
||||
QImage imageWithBackground( image.width(), image.height(), QImage::Format_RGB32 );
|
||||
QgsRenderChecker::drawBackground( &imageWithBackground );
|
||||
QPainter painter( &imageWithBackground );
|
||||
painter.drawImage( 0, 0, image );
|
||||
painter.end();
|
||||
|
||||
mReport += "<h2>" + testName + "</h2>\n";
|
||||
QString tempDir = QDir::tempPath() + QDir::separator();
|
||||
QString fileName = tempDir + testName + ".png";
|
||||
imageWithBackground.save( fileName, "PNG" );
|
||||
QgsRenderChecker checker;
|
||||
checker.setControlName( "expected_" + testName );
|
||||
checker.setRenderedImage( fileName );
|
||||
checker.setColorTolerance( 1 );
|
||||
bool resultFlag = checker.compareImages( testName, mismatchCount );
|
||||
mReport += checker.report();
|
||||
return resultFlag;
|
||||
}
|
||||
|
||||
QTEST_MAIN( TestQgsImageOperation )
|
||||
#include "testqgsimageoperation.moc"
|
BIN
tests/testdata/control_images/expected_imageop_bcnochange/expected_imageop_bcnochange.png
vendored
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
tests/testdata/control_images/expected_imageop_colorizefull/expected_imageop_colorizefull.png
vendored
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
tests/testdata/control_images/expected_imageop_colorizepartial/expected_imageop_colorizepartial.png
vendored
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
tests/testdata/control_images/expected_imageop_decreasebright/expected_imageop_decreasebright.png
vendored
Normal file
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 62 KiB |
BIN
tests/testdata/control_images/expected_imageop_decreasesat/expected_imageop_decreasesat.png
vendored
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
tests/testdata/control_images/expected_imageop_dt_interior/expected_imageop_dt_interior.png
vendored
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
tests/testdata/control_images/expected_imageop_dt_max/expected_imageop_dt_max.png
vendored
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
tests/testdata/control_images/expected_imageop_dt_spread/expected_imageop_dt_spread.png
vendored
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
tests/testdata/control_images/expected_imageop_fliphoz/expected_imageop_fliphoz.png
vendored
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
tests/testdata/control_images/expected_imageop_flipvert/expected_imageop_flipvert.png
vendored
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
tests/testdata/control_images/expected_imageop_grayaverage/expected_imageop_grayaverage.png
vendored
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
tests/testdata/control_images/expected_imageop_graylightness/expected_imageop_graylightness.png
vendored
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
tests/testdata/control_images/expected_imageop_grayluminosity/expected_imageop_grayluminosity.png
vendored
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
tests/testdata/control_images/expected_imageop_increasebright/expected_imageop_increasebright.png
vendored
Normal file
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 64 KiB |
BIN
tests/testdata/control_images/expected_imageop_increasesat/expected_imageop_increasesat.png
vendored
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
tests/testdata/control_images/expected_imageop_opacitydecrease/expected_imageop_opacitydecrease.png
vendored
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
tests/testdata/control_images/expected_imageop_opacityincrease/expected_imageop_opacityincrease.png
vendored
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
tests/testdata/control_images/expected_imageop_opacitynochange/expected_imageop_opacitynochange.png
vendored
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
tests/testdata/control_images/expected_imageop_overlaycolor/expected_imageop_overlaycolor.png
vendored
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
tests/testdata/control_images/expected_imageop_satnochange/expected_imageop_satnochange.png
vendored
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
tests/testdata/control_images/expected_imageop_smallimage/expected_imageop_smallimage.png
vendored
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
tests/testdata/control_images/expected_imageop_stackblur/expected_imageop_stackblur.png
vendored
Normal file
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 5.1 KiB |
BIN
tests/testdata/small_sample_image.png
vendored
Normal file
After Width: | Height: | Size: 5.6 KiB |