27#include <QtConcurrentMap>
31#define BLOCK_THREADS 16
37template <
typename PixelOperation>
38void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation,
QgsFeedback *feedback )
40 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
44 runPixelOperationOnWholeImage( image, operation, feedback );
49 QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation, feedback );
50 runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
54template <
typename PixelOperation>
55void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation,
QgsFeedback *feedback )
57 int height = image.height();
58 int width = image.width();
59 for (
int y = 0; y < height; ++y )
64 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
65 for (
int x = 0; x < width; ++x )
67 operation( ref[x], x, y );
74template <
typename RectOperation>
75void QgsImageOperation::runRectOperation( QImage &image, RectOperation &operation )
78 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
82 runRectOperationOnWholeImage( image, operation );
87 runBlockOperationInThreads( image, operation, ByRow );
91template <
class RectOperation>
92void QgsImageOperation::runRectOperationOnWholeImage( QImage &image, RectOperation &operation )
95 fullImage.beginLine = 0;
96 fullImage.endLine = image.height();
97 fullImage.lineLength = image.width();
98 fullImage.image = ℑ
100 operation( fullImage );
105template <
typename LineOperation>
106void QgsImageOperation::runLineOperation( QImage &image, LineOperation &operation,
QgsFeedback *feedback )
109 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
113 runLineOperationOnWholeImage( image, operation, feedback );
118 QgsImageOperation::ProcessBlockUsingLineOperation<LineOperation> blockOp( operation );
119 runBlockOperationInThreads( image, blockOp, operation.direction() );
123template <
class LineOperation>
124void QgsImageOperation::runLineOperationOnWholeImage( QImage &image, LineOperation &operation,
QgsFeedback *feedback )
126 int height = image.height();
127 int width = image.width();
130 int bpl = image.bytesPerLine();
131 if ( operation.direction() == ByRow )
133 for (
int y = 0; y < height; ++y )
138 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
139 operation( ref, width, bpl );
145 unsigned char *ref = image.scanLine( 0 );
146 for (
int x = 0; x < width; ++x, ref += 4 )
151 operation(
reinterpret_cast< QRgb *
>( ref ), height, bpl );
159template <
typename BlockOperation>
160void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction )
162 QList< ImageBlock > blocks;
163 unsigned int height = image.height();
164 unsigned int width = image.width();
166 unsigned int blockDimension1 = ( direction == QgsImageOperation::ByRow ) ? height : width;
167 unsigned int blockDimension2 = ( direction == QgsImageOperation::ByRow ) ? width : height;
171 unsigned int begin = 0;
173 for (
unsigned int block = 0; block <
BLOCK_THREADS; ++block, begin += blockLen )
176 newBlock.beginLine = begin;
178 newBlock.endLine = block < (
BLOCK_THREADS - 1 ) ? begin + blockLen : blockDimension1;
179 newBlock.lineLength = blockDimension2;
180 newBlock.image = ℑ
185 QtConcurrent::blockingMap( blocks, operation );
205 GrayscalePixelOperation operation( mode );
206 runPixelOperation( image, operation, feedback );
209void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
218 grayscaleLuminosityOp( rgb );
221 grayscaleAverageOp( rgb );
225 grayscaleLightnessOp( rgb );
230void QgsImageOperation::grayscaleLightnessOp( QRgb &rgb )
232 int red = qRed( rgb );
233 int green = qGreen( rgb );
234 int blue = qBlue( rgb );
236 int min = std::min( std::min( red, green ), blue );
237 int max = std::max( std::max( red, green ), blue );
239 int lightness = std::min( ( min + max ) / 2, 255 );
240 rgb = qRgba( lightness, lightness, lightness, qAlpha( rgb ) );
243void QgsImageOperation::grayscaleLuminosityOp( QRgb &rgb )
245 int luminosity = 0.21 * qRed( rgb ) + 0.72 * qGreen( rgb ) + 0.07 * qBlue( rgb );
246 rgb = qRgba( luminosity, luminosity, luminosity, qAlpha( rgb ) );
249void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )
251 int average = ( qRed( rgb ) + qGreen( rgb ) + qBlue( rgb ) ) / 3;
252 rgb = qRgba( average, average, average, qAlpha( rgb ) );
261 BrightnessContrastPixelOperation operation( brightness, contrast );
262 runPixelOperation( image, operation, feedback );
265void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
269 int red = adjustColorComponent( qRed( rgb ), mBrightness, mContrast );
270 int blue = adjustColorComponent( qBlue( rgb ), mBrightness, mContrast );
271 int green = adjustColorComponent( qGreen( rgb ), mBrightness, mContrast );
272 rgb = qRgba( red, green, blue, qAlpha( rgb ) );
275int QgsImageOperation::adjustColorComponent(
int colorComponent,
int brightness,
double contrastFactor )
277 return std::clamp(
static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 0, 255 );
285 HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0,
286 colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
287 runPixelOperation( image, operation, feedback );
290void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
294 QColor tmpColor( rgb );
296 tmpColor.getHsl( &h, &s, &l );
298 if ( mSaturation < 1.0 )
301 s = std::min(
static_cast< int >( s * mSaturation ), 255 );
303 else if ( mSaturation > 1.0 )
307 s = std::min(
static_cast< int >( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::pow( mSaturation, 2 ) ) ) ), 255 );
313 s = mColorizeSaturation;
314 if ( mColorizeStrength < 1.0 )
317 QColor colorizedColor = QColor::fromHsl( h, s, l );
318 int colorizedR, colorizedG, colorizedB;
319 colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
322 int r = mColorizeStrength * colorizedR + ( 1 - mColorizeStrength ) * tmpColor.red();
323 int g = mColorizeStrength * colorizedG + ( 1 - mColorizeStrength ) * tmpColor.green();
324 int b = mColorizeStrength * colorizedB + ( 1 - mColorizeStrength ) * tmpColor.blue();
326 rgb = qRgba( r, g, b, qAlpha( rgb ) );
331 tmpColor.setHsl( h, s, l, qAlpha( rgb ) );
332 rgb = tmpColor.rgba();
344 else if ( factor < 1.0 )
348 QColor transparentFillColor = QColor( 0, 0, 0, 255 * factor );
349 if ( image.format() == QImage::Format_Indexed8 )
350 image = image.convertToFormat( QImage::Format_ARGB32 );
354 QPainter painter( &image );
355 painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
356 painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
363 MultiplyOpacityPixelOperation operation( factor );
364 runPixelOperation( image, operation, feedback );
368void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
372 rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), std::clamp( std::round( mFactor * qAlpha( rgb ) ), 0.0, 255.0 ) );
379 QColor opaqueColor = color;
380 opaqueColor.setAlpha( 255 );
385 QPainter painter( &image );
386 painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
387 painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
395 if ( ! properties.
ramp )
397 QgsDebugError( QStringLiteral(
"no color ramp specified for distance transform" ) );
402 std::unique_ptr<double[]> array(
new double[
static_cast< qgssize >( image.width() ) * image.height()] );
407 ConvertToArrayPixelOperation convertToArray( image.width(), array.get(), properties.
shadeExterior );
408 runPixelOperation( image, convertToArray, feedback );
413 distanceTransform2d( array.get(), image.width(), image.height(), feedback );
420 spread = std::sqrt( maxValueInDistanceTransformArray( array.get(), image.width() * image.height() ) );
424 spread = properties.
spread;
431 ShadeFromArrayOperation shadeFromArray( image.width(), array.get(), spread, properties );
432 runPixelOperation( image, shadeFromArray, feedback );
435void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
440 if ( qAlpha( rgb ) > 0 )
443 mArray[ idx ] = 1 - qAlpha( rgb ) / 255.0;
454 if ( qAlpha( rgb ) == 255 )
468void QgsImageOperation::distanceTransform1d(
double *f,
int n,
int *v,
double *z,
double *d )
474 for (
int q = 1; q <= n - 1; q++ )
476 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
480 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
489 for (
int q = 0; q <= n - 1; q++ )
491 while ( z[k + 1] < q )
493 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
497double QgsImageOperation::maxValueInDistanceTransformArray(
const double *array,
const unsigned int size )
499 double dtMaxValue = array[0];
500 for (
unsigned int i = 1; i < size; ++i )
502 if ( array[i] > dtMaxValue )
504 dtMaxValue = array[i];
511void QgsImageOperation::distanceTransform2d(
double *im,
int width,
int height, QgsFeedback *feedback )
513 int maxDimension = std::max( width, height );
515 std::unique_ptr<double[]> f(
new double[ maxDimension ] );
516 std::unique_ptr<int []> v(
new int[ maxDimension ] );
517 std::unique_ptr<double[]>z(
new double[ maxDimension + 1 ] );
518 std::unique_ptr<double[]>d(
new double[ maxDimension ] );
521 for (
int x = 0; x < width; x++ )
526 for (
int y = 0; y < height; y++ )
528 f[y] = im[ x + y * width ];
530 distanceTransform1d( f.get(), height, v.get(), z.get(), d.get() );
531 for (
int y = 0; y < height; y++ )
533 im[ x + y * width ] = d[y];
538 for (
int y = 0; y < height; y++ )
543 for (
int x = 0; x < width; x++ )
545 f[x] = im[ x + y * width ];
547 distanceTransform1d( f.get(), width, v.get(), z.get(), d.get() );
548 for (
int x = 0; x < width; x++ )
550 im[ x + y * width ] = d[x];
555void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb,
const int x,
const int y )
557 if ( ! mProperties.ramp )
562 rgb = mProperties.ramp->color( 1.0 ).rgba();
566 int idx = y * mWidth + x;
569 double squaredVal = mArray[ idx ];
570 if ( squaredVal > mSpreadSquared )
572 rgb = Qt::transparent;
576 double distance = std::sqrt( squaredVal );
577 double val = distance / mSpread;
578 QColor rampColor = mProperties.ramp->color( val );
580 if ( ( mProperties.shadeExterior && distance > mSpread - 1 ) )
583 double alphaMultiplyFactor = mSpread - distance;
584 rampColor.setAlpha( rampColor.alpha() * alphaMultiplyFactor );
586 rgb = rampColor.rgba();
594 int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
595 int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
601 QImage::Format originalFormat = image.format();
602 QImage *pImage = ℑ
603 std::unique_ptr< QImage> convertedImage;
604 if ( !alphaOnly && originalFormat != QImage::Format_ARGB32_Premultiplied )
606 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
607 pImage = convertedImage.get();
609 else if ( alphaOnly && originalFormat != QImage::Format_ARGB32 )
611 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32 ) );
612 pImage = convertedImage.get();
623 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
625 StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn,
true, i1, i2, feedback );
626 runLineOperation( *pImage, topToBottomBlur, feedback );
631 StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow,
true, i1, i2, feedback );
632 runLineOperation( *pImage, leftToRightBlur, feedback );
637 StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn,
false, i1, i2, feedback );
638 runLineOperation( *pImage, bottomToTopBlur, feedback );
643 StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow,
false, i1, i2, feedback );
644 runLineOperation( *pImage, rightToLeftBlur, feedback );
649 if ( pImage->format() != originalFormat )
651 image = pImage->convertToFormat( originalFormat );
659 int width = image.width();
660 int height = image.height();
665 QImage *copy =
new QImage( image.copy() );
669 std::unique_ptr<double[]>kernel( createGaussianKernel( radius ) );
674 QImage::Format originalFormat = image.format();
675 QImage *pImage = ℑ
676 std::unique_ptr< QImage> convertedImage;
677 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
679 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
680 pImage = convertedImage.get();
690 QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
691 GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel.get(), feedback );
692 runRectOperation( *pImage, rowBlur );
698 auto yBlurImage = std::make_unique< QImage >( width, height, QImage::Format_ARGB32_Premultiplied );
699 GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage.get(), kernel.get(), feedback );
700 runRectOperation( xBlurImage, colBlur );
707 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
709 return new QImage( yBlurImage->convertToFormat( originalFormat ) );
712 return yBlurImage.release();
715void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
717 if ( mFeedback && mFeedback->isCanceled() )
720 int width = block.image->width();
721 int height = block.image->height();
722 int sourceBpl = block.image->bytesPerLine();
724 unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
725 QRgb *destRef =
nullptr;
726 if ( mDirection == ByRow )
728 unsigned char *sourceFirstLine = block.image->scanLine( 0 );
729 unsigned char *sourceRef;
732 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
734 if ( mFeedback && mFeedback->isCanceled() )
737 sourceRef = sourceFirstLine;
738 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
739 for (
int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
741 if ( mFeedback && mFeedback->isCanceled() )
744 *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
750 unsigned char *sourceRef = block.image->scanLine( block.beginLine );
751 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
753 if ( mFeedback && mFeedback->isCanceled() )
756 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
757 for (
int x = 0; x < width; ++x, ++destRef )
759 if ( mFeedback && mFeedback->isCanceled() )
762 *destRef = gaussianBlurHorizontal( x, sourceRef, width );
768inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical(
const int posy,
unsigned char *sourceFirstLine,
const int sourceBpl,
const int height )
const
777 for (
int i = 0; i <= mRadius * 2; ++i )
779 y = std::clamp( posy + ( i - mRadius ), 0, height - 1 );
780 ref = sourceFirstLine +
static_cast< std::size_t
>( sourceBpl ) * y;
782 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
783 r += mKernel[i] * qRed( *refRgb );
784 g += mKernel[i] * qGreen( *refRgb );
785 b += mKernel[i] * qBlue( *refRgb );
786 a += mKernel[i] * qAlpha( *refRgb );
789 return qRgba( r, g, b, a );
792inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal(
const int posx,
unsigned char *sourceFirstLine,
const int width )
const
801 for (
int i = 0; i <= mRadius * 2; ++i )
803 x = std::clamp( posx + ( i - mRadius ), 0, width - 1 );
804 ref = sourceFirstLine + x * 4;
806 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
807 r += mKernel[i] * qRed( *refRgb );
808 g += mKernel[i] * qGreen( *refRgb );
809 b += mKernel[i] * qBlue( *refRgb );
810 a += mKernel[i] * qAlpha( *refRgb );
813 return qRgba( r, g, b, a );
817double *QgsImageOperation::createGaussianKernel(
const int radius )
819 double *kernel =
new double[ radius * 2 + 1 ];
820 double sigma = radius / 3.0;
821 double twoSigmaSquared = 2 * sigma * sigma;
822 double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
823 double expCoefficient = -1.0 / twoSigmaSquared;
827 for (
int i = 0; i <= radius; ++i )
829 result = coefficient * std::exp( i * i * expCoefficient );
830 kernel[ radius - i ] = result;
834 kernel[radius + i] = result;
839 for (
int i = 0; i <= radius * 2; ++i )
853 runLineOperation( image, flipOperation );
858 int width = image.width();
859 int height = image.height();
866 for (
int y = 0; y < height; ++y )
869 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
870 for (
int x = 0; x < width; ++x )
872 if ( qAlpha( imgScanline[x] ) )
887 for (
int y = height - 1; y >= ymin; --y )
890 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
891 for (
int x = 0; x < width; ++x )
893 if ( qAlpha( imgScanline[x] ) )
896 xmin = std::min( xmin, x );
897 xmax = std::max( xmax, x );
907 for (
int y = ymin; y <= ymax; ++y )
909 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
910 for (
int x = 0; x < xmin; ++x )
912 if ( qAlpha( imgScanline[x] ) )
921 for (
int y = ymin; y <= ymax; ++y )
923 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
924 for (
int x = width - 1; x > xmax; --x )
926 if ( qAlpha( imgScanline[x] ) )
934 if ( minSize.isValid() )
936 if ( xmax - xmin < minSize.width() )
938 xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
939 xmax = xmin + minSize.width();
941 if ( ymax - ymin < minSize.height() )
943 ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
944 ymax = ymin + minSize.height();
950 const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
951 const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
952 xmin = std::max( 0, width / 2 - dx );
953 xmax = std::min( width, width / 2 + dx );
954 ymin = std::max( 0, height / 2 - dy );
955 ymax = std::min( height, height / 2 + dy );
958 return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
966void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef,
const int lineLength,
const int bytesPerLine )
const
968 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
971 unsigned char *p =
reinterpret_cast< unsigned char *
>( startRef );
972 unsigned char *tempLine =
new unsigned char[ lineLength * 4 ];
973 for (
int i = 0; i < lineLength * 4; ++i, p += increment )
975 tempLine[i++] = *( p++ );
976 tempLine[i++] = *( p++ );
977 tempLine[i++] = *( p++ );
978 tempLine[i] = *( p );
983 p =
reinterpret_cast< unsigned char *
>( startRef );
984 for (
int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
986 *( p++ ) = tempLine[i++];
987 *( p++ ) = tempLine[i++];
988 *( p++ ) = tempLine[i++];
989 *( p ) = tempLine[i];
Base class for feedback objects to be used for cancellation of something running in a worker thread.
bool isCanceled() const
Tells whether the operation has been canceled already.
static void adjustHueSaturation(QImage &image, double saturation, const QColor &colorizeColor=QColor(), double colorizeStrength=1.0, QgsFeedback *feedback=nullptr)
Alter the hue or saturation of a QImage.
static void multiplyOpacity(QImage &image, double factor, QgsFeedback *feedback=nullptr)
Multiplies opacity of image pixel values by a factor.
static void distanceTransform(QImage &image, const QgsImageOperation::DistanceTransformProperties &properties, QgsFeedback *feedback=nullptr)
Performs a distance transform on the source image and shades the result using a color ramp.
FlipType
Flip operation types.
@ FlipHorizontal
Flip the image horizontally.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
static void flipImage(QImage &image, FlipType type)
Flips an image horizontally or vertically.
static void adjustBrightnessContrast(QImage &image, int brightness, double contrast, QgsFeedback *feedback=nullptr)
Alter the brightness or contrast of a QImage.
static QImage * gaussianBlur(QImage &image, int radius, QgsFeedback *feedback=nullptr)
Performs a gaussian blur on an image.
static QRect nonTransparentImageRect(const QImage &image, QSize minSize=QSize(), bool center=false)
Calculates the non-transparent region of an image.
static void stackBlur(QImage &image, int radius, bool alphaOnly=false, QgsFeedback *feedback=nullptr)
Performs a stack blur on an image.
static QImage cropTransparent(const QImage &image, QSize minSize=QSize(), bool center=false)
Crop any transparent border from around an image.
static void convertToGrayscale(QImage &image, GrayscaleMode mode=GrayscaleLuminosity, QgsFeedback *feedback=nullptr)
Convert a QImage to a grayscale image.
GrayscaleMode
Modes for converting a QImage to grayscale.
@ 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.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
#define QgsDebugError(str)