23#include <QtConcurrentMap>
29#define BLOCK_THREADS 16
35template <
typename PixelOperation>
36void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation,
QgsFeedback *feedback )
38 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
42 runPixelOperationOnWholeImage( image, operation, feedback );
47 QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation, feedback );
48 runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
52template <
typename PixelOperation>
53void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation,
QgsFeedback *feedback )
55 int height = image.height();
56 int width = image.width();
57 for (
int y = 0; y < height; ++y )
62 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
63 for (
int x = 0; x < width; ++x )
65 operation( ref[x], x, y );
72template <
typename RectOperation>
73void QgsImageOperation::runRectOperation( QImage &image, RectOperation &operation )
76 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
80 runRectOperationOnWholeImage( image, operation );
85 runBlockOperationInThreads( image, operation, ByRow );
89template <
class RectOperation>
90void QgsImageOperation::runRectOperationOnWholeImage( QImage &image, RectOperation &operation )
93 fullImage.beginLine = 0;
94 fullImage.endLine = image.height();
95 fullImage.lineLength = image.width();
96 fullImage.image = ℑ
98 operation( fullImage );
103template <
typename LineOperation>
104void QgsImageOperation::runLineOperation( QImage &image, LineOperation &operation,
QgsFeedback *feedback )
107 if (
static_cast< qgssize >( image.height() ) * image.width() < 100000 )
111 runLineOperationOnWholeImage( image, operation, feedback );
116 QgsImageOperation::ProcessBlockUsingLineOperation<LineOperation> blockOp( operation );
117 runBlockOperationInThreads( image, blockOp, operation.direction() );
121template <
class LineOperation>
122void QgsImageOperation::runLineOperationOnWholeImage( QImage &image, LineOperation &operation,
QgsFeedback *feedback )
124 int height = image.height();
125 int width = image.width();
128 int bpl = image.bytesPerLine();
129 if ( operation.direction() == ByRow )
131 for (
int y = 0; y < height; ++y )
136 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
137 operation( ref, width, bpl );
143 unsigned char *ref = image.scanLine( 0 );
144 for (
int x = 0; x < width; ++x, ref += 4 )
149 operation(
reinterpret_cast< QRgb *
>( ref ), height, bpl );
157template <
typename BlockOperation>
158void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction )
160 QList< ImageBlock > blocks;
161 unsigned int height = image.height();
162 unsigned int width = image.width();
164 unsigned int blockDimension1 = ( direction == QgsImageOperation::ByRow ) ? height : width;
165 unsigned int blockDimension2 = ( direction == QgsImageOperation::ByRow ) ? width : height;
169 unsigned int begin = 0;
171 for (
unsigned int block = 0; block <
BLOCK_THREADS; ++block, begin += blockLen )
174 newBlock.beginLine = begin;
176 newBlock.endLine = block < (
BLOCK_THREADS - 1 ) ? begin + blockLen : blockDimension1;
177 newBlock.lineLength = blockDimension2;
178 newBlock.image = ℑ
183 QtConcurrent::blockingMap( blocks, operation );
203 GrayscalePixelOperation operation( mode );
204 runPixelOperation( image, operation, feedback );
207void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
215 case GrayscaleLuminosity:
216 grayscaleLuminosityOp( rgb );
218 case GrayscaleAverage:
219 grayscaleAverageOp( rgb );
221 case GrayscaleLightness:
223 grayscaleLightnessOp( rgb );
228void QgsImageOperation::grayscaleLightnessOp( QRgb &rgb )
230 int red = qRed( rgb );
231 int green = qGreen( rgb );
232 int blue = qBlue( rgb );
234 int min = std::min( std::min( red, green ), blue );
235 int max = std::max( std::max( red, green ), blue );
237 int lightness = std::min( ( min + max ) / 2, 255 );
238 rgb = qRgba( lightness, lightness, lightness, qAlpha( rgb ) );
241void QgsImageOperation::grayscaleLuminosityOp( QRgb &rgb )
243 int luminosity = 0.21 * qRed( rgb ) + 0.72 * qGreen( rgb ) + 0.07 * qBlue( rgb );
244 rgb = qRgba( luminosity, luminosity, luminosity, qAlpha( rgb ) );
247void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )
249 int average = ( qRed( rgb ) + qGreen( rgb ) + qBlue( rgb ) ) / 3;
250 rgb = qRgba( average, average, average, qAlpha( rgb ) );
259 BrightnessContrastPixelOperation operation( brightness, contrast );
260 runPixelOperation( image, operation, feedback );
263void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
const
267 int red = adjustColorComponent( qRed( rgb ), mBrightness, mContrast );
268 int blue = adjustColorComponent( qBlue( rgb ), mBrightness, mContrast );
269 int green = adjustColorComponent( qGreen( rgb ), mBrightness, mContrast );
270 rgb = qRgba( red, green, blue, qAlpha( rgb ) );
273int QgsImageOperation::adjustColorComponent(
int colorComponent,
int brightness,
double contrastFactor )
275 return std::clamp(
static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 0, 255 );
283 HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0,
284 colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
285 runPixelOperation( image, operation, feedback );
288void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
292 QColor tmpColor( rgb );
294 tmpColor.getHsl( &h, &s, &l );
296 if ( mSaturation < 1.0 )
299 s = std::min(
static_cast< int >( s * mSaturation ), 255 );
301 else if ( mSaturation > 1.0 )
305 s = std::min(
static_cast< int >( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::pow( mSaturation, 2 ) ) ) ), 255 );
311 s = mColorizeSaturation;
312 if ( mColorizeStrength < 1.0 )
315 QColor colorizedColor = QColor::fromHsl( h, s, l );
316 int colorizedR, colorizedG, colorizedB;
317 colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
320 int r = mColorizeStrength * colorizedR + ( 1 - mColorizeStrength ) * tmpColor.red();
321 int g = mColorizeStrength * colorizedG + ( 1 - mColorizeStrength ) * tmpColor.green();
322 int b = mColorizeStrength * colorizedB + ( 1 - mColorizeStrength ) * tmpColor.blue();
324 rgb = qRgba( r, g, b, qAlpha( rgb ) );
329 tmpColor.setHsl( h, s, l, qAlpha( rgb ) );
330 rgb = tmpColor.rgba();
342 else if ( factor < 1.0 )
346 QColor transparentFillColor = QColor( 0, 0, 0, 255 * factor );
347 if ( image.format() == QImage::Format_Indexed8 )
348 image = image.convertToFormat( QImage::Format_ARGB32 );
352 QPainter painter( &image );
353 painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
354 painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
361 MultiplyOpacityPixelOperation operation( factor );
362 runPixelOperation( image, operation, feedback );
366void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
370 rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), std::clamp( std::round( mFactor * qAlpha( rgb ) ), 0.0, 255.0 ) );
377 QColor opaqueColor = color;
378 opaqueColor.setAlpha( 255 );
383 QPainter painter( &image );
384 painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
385 painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
393 if ( ! properties.
ramp )
395 QgsDebugError( QStringLiteral(
"no color ramp specified for distance transform" ) );
400 std::unique_ptr<double[]> array(
new double[
static_cast< qgssize >( image.width() ) * image.height()] );
405 ConvertToArrayPixelOperation convertToArray( image.width(), array.get(), properties.
shadeExterior );
406 runPixelOperation( image, convertToArray, feedback );
411 distanceTransform2d( array.get(), image.width(), image.height(), feedback );
418 spread = std::sqrt( maxValueInDistanceTransformArray( array.get(), image.width() * image.height() ) );
422 spread = properties.
spread;
429 ShadeFromArrayOperation shadeFromArray( image.width(), array.get(), spread, properties );
430 runPixelOperation( image, shadeFromArray, feedback );
433void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
438 if ( qAlpha( rgb ) > 0 )
441 mArray[ idx ] = 1 - qAlpha( rgb ) / 255.0;
452 if ( qAlpha( rgb ) == 255 )
466void QgsImageOperation::distanceTransform1d(
double *f,
int n,
int *v,
double *z,
double *d )
472 for (
int q = 1; q <= n - 1; q++ )
474 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
478 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
487 for (
int q = 0; q <= n - 1; q++ )
489 while ( z[k + 1] < q )
491 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
495double QgsImageOperation::maxValueInDistanceTransformArray(
const double *array,
const unsigned int size )
497 double dtMaxValue = array[0];
498 for (
unsigned int i = 1; i < size; ++i )
500 if ( array[i] > dtMaxValue )
502 dtMaxValue = array[i];
509void QgsImageOperation::distanceTransform2d(
double *im,
int width,
int height,
QgsFeedback *feedback )
511 int maxDimension = std::max( width, height );
513 std::unique_ptr<double[]> f(
new double[ maxDimension ] );
514 std::unique_ptr<int []> v(
new int[ maxDimension ] );
515 std::unique_ptr<double[]>z(
new double[ maxDimension + 1 ] );
516 std::unique_ptr<double[]>d(
new double[ maxDimension ] );
519 for (
int x = 0; x < width; x++ )
524 for (
int y = 0; y < height; y++ )
526 f[y] = im[ x + y * width ];
528 distanceTransform1d( f.get(), height, v.get(), z.get(), d.get() );
529 for (
int y = 0; y < height; y++ )
531 im[ x + y * width ] = d[y];
536 for (
int y = 0; y < height; y++ )
541 for (
int x = 0; x < width; x++ )
543 f[x] = im[ x + y * width ];
545 distanceTransform1d( f.get(), width, v.get(), z.get(), d.get() );
546 for (
int x = 0; x < width; x++ )
548 im[ x + y * width ] = d[x];
553void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb,
const int x,
const int y )
555 if ( ! mProperties.ramp )
560 rgb = mProperties.ramp->color( 1.0 ).rgba();
564 int idx = y * mWidth + x;
567 double squaredVal = mArray[ idx ];
568 if ( squaredVal > mSpreadSquared )
570 rgb = Qt::transparent;
574 double distance = std::sqrt( squaredVal );
575 double val = distance / mSpread;
576 QColor rampColor = mProperties.ramp->color( val );
578 if ( ( mProperties.shadeExterior && distance > mSpread - 1 ) )
581 double alphaMultiplyFactor = mSpread - distance;
582 rampColor.setAlpha( rampColor.alpha() * alphaMultiplyFactor );
584 rgb = rampColor.rgba();
592 int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
593 int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
599 QImage::Format originalFormat = image.format();
600 QImage *pImage = ℑ
601 std::unique_ptr< QImage> convertedImage;
602 if ( !alphaOnly && originalFormat != QImage::Format_ARGB32_Premultiplied )
604 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
605 pImage = convertedImage.get();
607 else if ( alphaOnly && originalFormat != QImage::Format_ARGB32 )
609 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32 ) );
610 pImage = convertedImage.get();
621 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
623 StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn,
true, i1, i2, feedback );
624 runLineOperation( *pImage, topToBottomBlur, feedback );
629 StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow,
true, i1, i2, feedback );
630 runLineOperation( *pImage, leftToRightBlur, feedback );
635 StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn,
false, i1, i2, feedback );
636 runLineOperation( *pImage, bottomToTopBlur, feedback );
641 StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow,
false, i1, i2, feedback );
642 runLineOperation( *pImage, rightToLeftBlur, feedback );
647 if ( pImage->format() != originalFormat )
649 image = pImage->convertToFormat( originalFormat );
657 int width = image.width();
658 int height = image.height();
663 QImage *copy =
new QImage( image.copy() );
667 std::unique_ptr<double[]>kernel( createGaussianKernel( radius ) );
672 QImage::Format originalFormat = image.format();
673 QImage *pImage = ℑ
674 std::unique_ptr< QImage> convertedImage;
675 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
677 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
678 pImage = convertedImage.get();
688 QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
689 GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel.get(), feedback );
690 runRectOperation( *pImage, rowBlur );
696 std::unique_ptr< QImage > yBlurImage = std::make_unique< QImage >( width, height, QImage::Format_ARGB32_Premultiplied );
697 GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage.get(), kernel.get(), feedback );
698 runRectOperation( xBlurImage, colBlur );
705 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
707 return new QImage( yBlurImage->convertToFormat( originalFormat ) );
710 return yBlurImage.release();
713void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
715 if ( mFeedback && mFeedback->isCanceled() )
718 int width = block.image->width();
719 int height = block.image->height();
720 int sourceBpl = block.image->bytesPerLine();
722 unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
723 QRgb *destRef =
nullptr;
724 if ( mDirection == ByRow )
726 unsigned char *sourceFirstLine = block.image->scanLine( 0 );
727 unsigned char *sourceRef;
730 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
732 if ( mFeedback && mFeedback->isCanceled() )
735 sourceRef = sourceFirstLine;
736 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
737 for (
int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
739 if ( mFeedback && mFeedback->isCanceled() )
742 *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
748 unsigned char *sourceRef = block.image->scanLine( block.beginLine );
749 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
751 if ( mFeedback && mFeedback->isCanceled() )
754 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
755 for (
int x = 0; x < width; ++x, ++destRef )
757 if ( mFeedback && mFeedback->isCanceled() )
760 *destRef = gaussianBlurHorizontal( x, sourceRef, width );
766inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical(
const int posy,
unsigned char *sourceFirstLine,
const int sourceBpl,
const int height )
const
775 for (
int i = 0; i <= mRadius * 2; ++i )
777 y = std::clamp( posy + ( i - mRadius ), 0, height - 1 );
778 ref = sourceFirstLine +
static_cast< std::size_t
>( sourceBpl ) * y;
780 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
781 r += mKernel[i] * qRed( *refRgb );
782 g += mKernel[i] * qGreen( *refRgb );
783 b += mKernel[i] * qBlue( *refRgb );
784 a += mKernel[i] * qAlpha( *refRgb );
787 return qRgba( r, g, b, a );
790inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal(
const int posx,
unsigned char *sourceFirstLine,
const int width )
const
799 for (
int i = 0; i <= mRadius * 2; ++i )
801 x = std::clamp( posx + ( i - mRadius ), 0, width - 1 );
802 ref = sourceFirstLine + x * 4;
804 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
805 r += mKernel[i] * qRed( *refRgb );
806 g += mKernel[i] * qGreen( *refRgb );
807 b += mKernel[i] * qBlue( *refRgb );
808 a += mKernel[i] * qAlpha( *refRgb );
811 return qRgba( r, g, b, a );
815double *QgsImageOperation::createGaussianKernel(
const int radius )
817 double *kernel =
new double[ radius * 2 + 1 ];
818 double sigma = radius / 3.0;
819 double twoSigmaSquared = 2 * sigma * sigma;
820 double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
821 double expCoefficient = -1.0 / twoSigmaSquared;
825 for (
int i = 0; i <= radius; ++i )
827 result = coefficient * std::exp( i * i * expCoefficient );
828 kernel[ radius - i ] = result;
832 kernel[radius + i] = result;
837 for (
int i = 0; i <= radius * 2; ++i )
851 runLineOperation( image, flipOperation );
856 int width = image.width();
857 int height = image.height();
864 for (
int y = 0; y < height; ++y )
867 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
868 for (
int x = 0; x < width; ++x )
870 if ( qAlpha( imgScanline[x] ) )
885 for (
int y = height - 1; y >= ymin; --y )
888 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
889 for (
int x = 0; x < width; ++x )
891 if ( qAlpha( imgScanline[x] ) )
894 xmin = std::min( xmin, x );
895 xmax = std::max( xmax, x );
905 for (
int y = ymin; y <= ymax; ++y )
907 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
908 for (
int x = 0; x < xmin; ++x )
910 if ( qAlpha( imgScanline[x] ) )
919 for (
int y = ymin; y <= ymax; ++y )
921 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
922 for (
int x = width - 1; x > xmax; --x )
924 if ( qAlpha( imgScanline[x] ) )
932 if ( minSize.isValid() )
934 if ( xmax - xmin < minSize.width() )
936 xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
937 xmax = xmin + minSize.width();
939 if ( ymax - ymin < minSize.height() )
941 ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
942 ymax = ymin + minSize.height();
948 const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
949 const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
950 xmin = std::max( 0, width / 2 - dx );
951 xmax = std::min( width, width / 2 + dx );
952 ymin = std::max( 0, height / 2 - dy );
953 ymax = std::min( height, height / 2 + dy );
956 return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
964void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef,
const int lineLength,
const int bytesPerLine )
const
966 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
969 unsigned char *p =
reinterpret_cast< unsigned char *
>( startRef );
970 unsigned char *tempLine =
new unsigned char[ lineLength * 4 ];
971 for (
int i = 0; i < lineLength * 4; ++i, p += increment )
973 tempLine[i++] = *( p++ );
974 tempLine[i++] = *( p++ );
975 tempLine[i++] = *( p++ );
976 tempLine[i] = *( p );
981 p =
reinterpret_cast< unsigned char *
>( startRef );
982 for (
int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
984 *( p++ ) = tempLine[i++];
985 *( p++ ) = tempLine[i++];
986 *( p++ ) = tempLine[i++];
987 *( 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.
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)