28#include <QtConcurrentMap>
30using namespace Qt::StringLiterals;
34#define BLOCK_THREADS 16
40template <
typename PixelOperation>
41void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation,
QgsFeedback *feedback )
43 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
47 runPixelOperationOnWholeImage( image, operation, feedback );
52 QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation, feedback );
53 runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
57template <
typename PixelOperation>
58void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation,
QgsFeedback *feedback )
60 int height = image.height();
61 int width = image.width();
62 for (
int y = 0; y < height; ++y )
67 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
68 for (
int x = 0; x < width; ++x )
70 operation( ref[x], x, y );
77template <
typename RectOperation>
78void QgsImageOperation::runRectOperation( QImage &image, RectOperation &operation )
81 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
85 runRectOperationOnWholeImage( image, operation );
90 runBlockOperationInThreads( image, operation, ByRow );
94template <
class RectOperation>
95void QgsImageOperation::runRectOperationOnWholeImage( QImage &image, RectOperation &operation )
98 fullImage.beginLine = 0;
99 fullImage.endLine = image.height();
100 fullImage.lineLength = image.width();
101 fullImage.image = ℑ
103 operation( fullImage );
108template <
typename LineOperation>
109void QgsImageOperation::runLineOperation( QImage &image, LineOperation &operation,
QgsFeedback *feedback )
112 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
116 runLineOperationOnWholeImage( image, operation, feedback );
121 QgsImageOperation::ProcessBlockUsingLineOperation<LineOperation> blockOp( operation );
122 runBlockOperationInThreads( image, blockOp, operation.direction() );
126template <
class LineOperation>
127void QgsImageOperation::runLineOperationOnWholeImage( QImage &image, LineOperation &operation,
QgsFeedback *feedback )
129 int height = image.height();
130 int width = image.width();
133 int bpl = image.bytesPerLine();
134 if ( operation.direction() == ByRow )
136 for (
int y = 0; y < height; ++y )
141 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
142 operation( ref, width, bpl );
148 unsigned char *ref = image.scanLine( 0 );
149 for (
int x = 0; x < width; ++x, ref += 4 )
154 operation(
reinterpret_cast< QRgb *
>( ref ), height, bpl );
162template <
typename BlockOperation>
163void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction )
165 QList< ImageBlock > blocks;
166 unsigned int height = image.height();
167 unsigned int width = image.width();
169 unsigned int blockDimension1 = ( direction == QgsImageOperation::ByRow ) ? height : width;
170 unsigned int blockDimension2 = ( direction == QgsImageOperation::ByRow ) ? width : height;
174 unsigned int begin = 0;
176 for (
unsigned int block = 0; block <
BLOCK_THREADS; ++block, begin += blockLen )
179 newBlock.beginLine = begin;
181 newBlock.endLine = block < (
BLOCK_THREADS - 1 ) ? begin + blockLen : blockDimension1;
182 newBlock.lineLength = blockDimension2;
183 newBlock.image = ℑ
188 QtConcurrent::blockingMap( blocks, operation );
208 GrayscalePixelOperation operation( mode );
209 runPixelOperation( image, operation, feedback );
212void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
221 grayscaleLuminosityOp( rgb );
224 grayscaleAverageOp( rgb );
228 grayscaleLightnessOp( rgb );
233void QgsImageOperation::grayscaleLightnessOp( QRgb &rgb )
235 int red = qRed( rgb );
236 int green = qGreen( rgb );
237 int blue = qBlue( rgb );
239 int min = std::min( std::min( red, green ), blue );
240 int max = std::max( std::max( red, green ), blue );
242 int lightness = std::min( ( min + max ) / 2, 255 );
243 rgb = qRgba( lightness, lightness, lightness, qAlpha( rgb ) );
246void QgsImageOperation::grayscaleLuminosityOp( QRgb &rgb )
248 int luminosity = 0.21 * qRed( rgb ) + 0.72 * qGreen( rgb ) + 0.07 * qBlue( rgb );
249 rgb = qRgba( luminosity, luminosity, luminosity, qAlpha( rgb ) );
252void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )
254 int average = ( qRed( rgb ) + qGreen( rgb ) + qBlue( rgb ) ) / 3;
255 rgb = qRgba( average, average, average, qAlpha( rgb ) );
264 BrightnessContrastPixelOperation operation( brightness, contrast );
265 runPixelOperation( image, operation, feedback );
268void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
272 int red = adjustColorComponent( qRed( rgb ), mBrightness, mContrast );
273 int blue = adjustColorComponent( qBlue( rgb ), mBrightness, mContrast );
274 int green = adjustColorComponent( qGreen( rgb ), mBrightness, mContrast );
275 rgb = qRgba( red, green, blue, qAlpha( rgb ) );
278int QgsImageOperation::adjustColorComponent(
int colorComponent,
int brightness,
double contrastFactor )
280 return std::clamp(
static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 0, 255 );
288 HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0,
289 colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
290 runPixelOperation( image, operation, feedback );
293void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
297 QColor tmpColor( rgb );
299 tmpColor.getHsl( &h, &s, &l );
301 if ( mSaturation < 1.0 )
304 s = std::min(
static_cast< int >( s * mSaturation ), 255 );
306 else if ( mSaturation > 1.0 )
310 s = std::min(
static_cast< int >( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::pow( mSaturation, 2 ) ) ) ), 255 );
316 s = mColorizeSaturation;
317 if ( mColorizeStrength < 1.0 )
320 QColor colorizedColor = QColor::fromHsl( h, s, l );
321 int colorizedR, colorizedG, colorizedB;
322 colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
325 int r = mColorizeStrength * colorizedR + ( 1 - mColorizeStrength ) * tmpColor.red();
326 int g = mColorizeStrength * colorizedG + ( 1 - mColorizeStrength ) * tmpColor.green();
327 int b = mColorizeStrength * colorizedB + ( 1 - mColorizeStrength ) * tmpColor.blue();
329 rgb = qRgba( r, g, b, qAlpha( rgb ) );
334 tmpColor.setHsl( h, s, l, qAlpha( rgb ) );
335 rgb = tmpColor.rgba();
347 else if ( factor < 1.0 )
351 QColor transparentFillColor = QColor( 0, 0, 0, 255 * factor );
352 if ( image.format() == QImage::Format_Indexed8 )
353 image = image.convertToFormat( QImage::Format_ARGB32 );
357 QPainter painter( &image );
358 painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
359 painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
366 MultiplyOpacityPixelOperation operation( factor );
367 runPixelOperation( image, operation, feedback );
371void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
375 rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), std::clamp( std::round( mFactor * qAlpha( rgb ) ), 0.0, 255.0 ) );
382 QColor opaqueColor = color;
383 opaqueColor.setAlpha( 255 );
388 QPainter painter( &image );
389 painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
390 painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
398 if ( ! properties.
ramp )
400 QgsDebugError( u
"no color ramp specified for distance transform"_s );
405 std::unique_ptr<double[]> array(
new double[
static_cast< qgssize >( image.width() ) * image.height()] );
410 ConvertToArrayPixelOperation convertToArray( image.width(), array.get(), properties.
shadeExterior );
411 runPixelOperation( image, convertToArray, feedback );
416 distanceTransform2d( array.get(), image.width(), image.height(), feedback );
423 spread = std::sqrt( maxValueInDistanceTransformArray( array.get(), image.width() * image.height() ) );
427 spread = properties.
spread;
434 ShadeFromArrayOperation shadeFromArray( image.width(), array.get(), spread, properties );
435 runPixelOperation( image, shadeFromArray, feedback );
438void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
443 if ( qAlpha( rgb ) > 0 )
446 mArray[ idx ] = 1 - qAlpha( rgb ) / 255.0;
457 if ( qAlpha( rgb ) == 255 )
471void QgsImageOperation::distanceTransform1d(
double *f,
int n,
int *v,
double *z,
double *d )
477 for (
int q = 1; q <= n - 1; q++ )
479 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
483 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
492 for (
int q = 0; q <= n - 1; q++ )
494 while ( z[k + 1] < q )
496 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
500double QgsImageOperation::maxValueInDistanceTransformArray(
const double *array,
const unsigned int size )
502 double dtMaxValue = array[0];
503 for (
unsigned int i = 1; i < size; ++i )
505 if ( array[i] > dtMaxValue )
507 dtMaxValue = array[i];
514void QgsImageOperation::distanceTransform2d(
double *im,
int width,
int height, QgsFeedback *feedback )
516 int maxDimension = std::max( width, height );
518 std::unique_ptr<double[]> f(
new double[ maxDimension ] );
519 std::unique_ptr<int []> v(
new int[ maxDimension ] );
520 std::unique_ptr<double[]>z(
new double[ maxDimension + 1 ] );
521 std::unique_ptr<double[]>d(
new double[ maxDimension ] );
524 for (
int x = 0; x < width; x++ )
529 for (
int y = 0; y < height; y++ )
531 f[y] = im[ x + y * width ];
533 distanceTransform1d( f.get(), height, v.get(), z.get(), d.get() );
534 for (
int y = 0; y < height; y++ )
536 im[ x + y * width ] = d[y];
541 for (
int y = 0; y < height; y++ )
546 for (
int x = 0; x < width; x++ )
548 f[x] = im[ x + y * width ];
550 distanceTransform1d( f.get(), width, v.get(), z.get(), d.get() );
551 for (
int x = 0; x < width; x++ )
553 im[ x + y * width ] = d[x];
558void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb,
const int x,
const int y )
560 if ( ! mProperties.ramp )
565 rgb = mProperties.ramp->color( 1.0 ).rgba();
569 int idx = y * mWidth + x;
572 double squaredVal = mArray[ idx ];
573 if ( squaredVal > mSpreadSquared )
575 rgb = Qt::transparent;
579 double distance = std::sqrt( squaredVal );
580 double val = distance / mSpread;
581 QColor rampColor = mProperties.ramp->color( val );
583 if ( ( mProperties.shadeExterior && distance > mSpread - 1 ) )
586 double alphaMultiplyFactor = mSpread - distance;
587 rampColor.setAlpha( rampColor.alpha() * alphaMultiplyFactor );
589 rgb = rampColor.rgba();
597 int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
598 int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
604 QImage::Format originalFormat = image.format();
605 QImage *pImage = ℑ
606 std::unique_ptr< QImage> convertedImage;
607 if ( !alphaOnly && originalFormat != QImage::Format_ARGB32_Premultiplied )
609 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
610 pImage = convertedImage.get();
612 else if ( alphaOnly && originalFormat != QImage::Format_ARGB32 )
614 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32 ) );
615 pImage = convertedImage.get();
626 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
628 StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn,
true, i1, i2, feedback );
629 runLineOperation( *pImage, topToBottomBlur, feedback );
634 StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow,
true, i1, i2, feedback );
635 runLineOperation( *pImage, leftToRightBlur, feedback );
640 StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn,
false, i1, i2, feedback );
641 runLineOperation( *pImage, bottomToTopBlur, feedback );
646 StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow,
false, i1, i2, feedback );
647 runLineOperation( *pImage, rightToLeftBlur, feedback );
652 if ( pImage->format() != originalFormat )
654 image = pImage->convertToFormat( originalFormat );
662 int width = image.width();
663 int height = image.height();
668 QImage *copy =
new QImage( image.copy() );
672 std::unique_ptr<double[]>kernel( createGaussianKernel( radius ) );
677 QImage::Format originalFormat = image.format();
678 QImage *pImage = ℑ
679 std::unique_ptr< QImage> convertedImage;
680 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
682 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
683 pImage = convertedImage.get();
693 QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
694 GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel.get(), feedback );
695 runRectOperation( *pImage, rowBlur );
701 auto yBlurImage = std::make_unique< QImage >( width, height, QImage::Format_ARGB32_Premultiplied );
702 GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage.get(), kernel.get(), feedback );
703 runRectOperation( xBlurImage, colBlur );
710 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
712 return new QImage( yBlurImage->convertToFormat( originalFormat ) );
715 return yBlurImage.release();
718void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
720 if ( mFeedback && mFeedback->isCanceled() )
723 int width = block.image->width();
724 int height = block.image->height();
725 int sourceBpl = block.image->bytesPerLine();
727 unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
728 QRgb *destRef =
nullptr;
729 if ( mDirection == ByRow )
731 unsigned char *sourceFirstLine = block.image->scanLine( 0 );
732 unsigned char *sourceRef;
735 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
737 if ( mFeedback && mFeedback->isCanceled() )
740 sourceRef = sourceFirstLine;
741 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
742 for (
int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
744 if ( mFeedback && mFeedback->isCanceled() )
747 *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
753 unsigned char *sourceRef = block.image->scanLine( block.beginLine );
754 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
756 if ( mFeedback && mFeedback->isCanceled() )
759 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
760 for (
int x = 0; x < width; ++x, ++destRef )
762 if ( mFeedback && mFeedback->isCanceled() )
765 *destRef = gaussianBlurHorizontal( x, sourceRef, width );
771inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical(
const int posy,
unsigned char *sourceFirstLine,
const int sourceBpl,
const int height )
const
780 for (
int i = 0; i <= mRadius * 2; ++i )
782 y = std::clamp( posy + ( i - mRadius ), 0, height - 1 );
783 ref = sourceFirstLine +
static_cast< std::size_t
>( sourceBpl ) * y;
785 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
786 r += mKernel[i] * qRed( *refRgb );
787 g += mKernel[i] * qGreen( *refRgb );
788 b += mKernel[i] * qBlue( *refRgb );
789 a += mKernel[i] * qAlpha( *refRgb );
792 return qRgba( r, g, b, a );
795inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal(
const int posx,
unsigned char *sourceFirstLine,
const int width )
const
804 for (
int i = 0; i <= mRadius * 2; ++i )
806 x = std::clamp( posx + ( i - mRadius ), 0, width - 1 );
807 ref = sourceFirstLine + x * 4;
809 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
810 r += mKernel[i] * qRed( *refRgb );
811 g += mKernel[i] * qGreen( *refRgb );
812 b += mKernel[i] * qBlue( *refRgb );
813 a += mKernel[i] * qAlpha( *refRgb );
816 return qRgba( r, g, b, a );
820double *QgsImageOperation::createGaussianKernel(
const int radius )
822 double *kernel =
new double[ radius * 2 + 1 ];
823 double sigma = radius / 3.0;
824 double twoSigmaSquared = 2 * sigma * sigma;
825 double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
826 double expCoefficient = -1.0 / twoSigmaSquared;
830 for (
int i = 0; i <= radius; ++i )
832 result = coefficient * std::exp( i * i * expCoefficient );
833 kernel[ radius - i ] = result;
837 kernel[radius + i] = result;
842 for (
int i = 0; i <= radius * 2; ++i )
856 runLineOperation( image, flipOperation );
861 int width = image.width();
862 int height = image.height();
869 for (
int y = 0; y < height; ++y )
872 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
873 for (
int x = 0; x < width; ++x )
875 if ( qAlpha( imgScanline[x] ) )
890 for (
int y = height - 1; y >= ymin; --y )
893 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
894 for (
int x = 0; x < width; ++x )
896 if ( qAlpha( imgScanline[x] ) )
899 xmin = std::min( xmin, x );
900 xmax = std::max( xmax, x );
910 for (
int y = ymin; y <= ymax; ++y )
912 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
913 for (
int x = 0; x < xmin; ++x )
915 if ( qAlpha( imgScanline[x] ) )
924 for (
int y = ymin; y <= ymax; ++y )
926 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
927 for (
int x = width - 1; x > xmax; --x )
929 if ( qAlpha( imgScanline[x] ) )
937 if ( minSize.isValid() )
939 if ( xmax - xmin < minSize.width() )
941 xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
942 xmax = xmin + minSize.width();
944 if ( ymax - ymin < minSize.height() )
946 ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
947 ymax = ymin + minSize.height();
953 const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
954 const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
955 xmin = std::max( 0, width / 2 - dx );
956 xmax = std::min( width, width / 2 + dx );
957 ymin = std::max( 0, height / 2 - dy );
958 ymax = std::min( height, height / 2 + dy );
961 return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
969void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef,
const int lineLength,
const int bytesPerLine )
const
971 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
974 unsigned char *p =
reinterpret_cast< unsigned char *
>( startRef );
975 unsigned char *tempLine =
new unsigned char[ lineLength * 4 ];
976 for (
int i = 0; i < lineLength * 4; ++i, p += increment )
978 tempLine[i++] = *( p++ );
979 tempLine[i++] = *( p++ );
980 tempLine[i++] = *( p++ );
981 tempLine[i] = *( p );
986 p =
reinterpret_cast< unsigned char *
>( startRef );
987 for (
int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
989 *( p++ ) = tempLine[i++];
990 *( p++ ) = tempLine[i++];
991 *( p++ ) = tempLine[i++];
992 *( 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)