22 #include <QtConcurrentMap>
28 #define BLOCK_THREADS 16
34 template <
typename PixelOperation>
35 void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation )
37 if ( image.height() * image.width() < 100000 )
41 runPixelOperationOnWholeImage( image, operation );
46 QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation );
47 runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
51 template <
typename PixelOperation>
52 void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation )
54 int height = image.height();
55 int width = image.width();
56 for (
int y = 0; y < height; ++y )
58 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
59 for (
int x = 0; x < width; ++x )
61 operation( ref[x], x, y );
68 template <
typename RectOperation>
69 void QgsImageOperation::runRectOperation( QImage &image, RectOperation &operation )
72 if ( image.height() * image.width() < 100000 )
76 runRectOperationOnWholeImage( image, operation );
81 runBlockOperationInThreads( image, operation, ByRow );
85 template <
class RectOperation>
86 void QgsImageOperation::runRectOperationOnWholeImage( QImage &image, RectOperation &operation )
89 fullImage.beginLine = 0;
90 fullImage.endLine = image.height();
91 fullImage.lineLength = image.width();
92 fullImage.image = ℑ
94 operation( fullImage );
99 template <
typename LineOperation>
100 void QgsImageOperation::runLineOperation( QImage &image, LineOperation &operation )
103 if ( image.height() * image.width() < 100000 )
107 runLineOperationOnWholeImage( image, operation );
112 QgsImageOperation::ProcessBlockUsingLineOperation<LineOperation> blockOp( operation );
113 runBlockOperationInThreads( image, blockOp, operation.direction() );
117 template <
class LineOperation>
118 void QgsImageOperation::runLineOperationOnWholeImage( QImage &image, LineOperation &operation )
120 int height = image.height();
121 int width = image.width();
124 int bpl = image.bytesPerLine();
125 if ( operation.direction() == ByRow )
127 for (
int y = 0; y < height; ++y )
129 QRgb *ref =
reinterpret_cast< QRgb *
>( image.scanLine( y ) );
130 operation( ref, width, bpl );
136 unsigned char *ref = image.scanLine( 0 );
137 for (
int x = 0; x < width; ++x, ref += 4 )
139 operation(
reinterpret_cast< QRgb *
>( ref ), height, bpl );
147 template <
typename BlockOperation>
148 void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction )
150 QList< ImageBlock > blocks;
151 unsigned int height = image.height();
152 unsigned int width = image.width();
154 unsigned int blockDimension1 = ( direction == QgsImageOperation::ByRow ) ? height : width;
155 unsigned int blockDimension2 = ( direction == QgsImageOperation::ByRow ) ? width : height;
159 unsigned int begin = 0;
161 for (
unsigned int block = 0; block <
BLOCK_THREADS; ++block, begin += blockLen )
164 newBlock.beginLine = begin;
166 newBlock.endLine = block < (
BLOCK_THREADS - 1 ) ? begin + blockLen : blockDimension1;
167 newBlock.lineLength = blockDimension2;
168 newBlock.image = ℑ
173 QtConcurrent::blockingMap( blocks, operation );
192 GrayscalePixelOperation operation( mode );
193 runPixelOperation( image, operation );
196 void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
204 case GrayscaleLuminosity:
205 grayscaleLuminosityOp( rgb );
207 case GrayscaleAverage:
208 grayscaleAverageOp( rgb );
210 case GrayscaleLightness:
212 grayscaleLightnessOp( rgb );
217 void QgsImageOperation::grayscaleLightnessOp( QRgb &rgb )
219 int red = qRed( rgb );
220 int green = qGreen( rgb );
221 int blue = qBlue( rgb );
223 int min = std::min( std::min( red, green ), blue );
224 int max = std::max( std::max( red, green ), blue );
226 int lightness = std::min( ( min + max ) / 2, 255 );
227 rgb = qRgba( lightness, lightness, lightness, qAlpha( rgb ) );
230 void QgsImageOperation::grayscaleLuminosityOp( QRgb &rgb )
232 int luminosity = 0.21 * qRed( rgb ) + 0.72 * qGreen( rgb ) + 0.07 * qBlue( rgb );
233 rgb = qRgba( luminosity, luminosity, luminosity, qAlpha( rgb ) );
236 void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )
238 int average = ( qRed( rgb ) + qGreen( rgb ) + qBlue( rgb ) ) / 3;
239 rgb = qRgba( average, average, average, qAlpha( rgb ) );
247 BrightnessContrastPixelOperation operation( brightness, contrast );
248 runPixelOperation( image, operation );
251 void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
255 int red = adjustColorComponent( qRed( rgb ), mBrightness, mContrast );
256 int blue = adjustColorComponent( qBlue( rgb ), mBrightness, mContrast );
257 int green = adjustColorComponent( qGreen( rgb ), mBrightness, mContrast );
258 rgb = qRgba( red, green, blue, qAlpha( rgb ) );
261 int QgsImageOperation::adjustColorComponent(
int colorComponent,
int brightness,
double contrastFactor )
263 return std::clamp(
static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 0, 255 );
270 HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0,
271 colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
272 runPixelOperation( image, operation );
275 void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
279 QColor tmpColor( rgb );
281 tmpColor.getHsl( &h, &s, &l );
283 if ( mSaturation < 1.0 )
286 s = std::min(
static_cast< int >( s * mSaturation ), 255 );
288 else if ( mSaturation > 1.0 )
292 s = std::min(
static_cast< int >( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::pow( mSaturation, 2 ) ) ) ), 255 );
298 s = mColorizeSaturation;
299 if ( mColorizeStrength < 1.0 )
302 QColor colorizedColor = QColor::fromHsl( h, s, l );
303 int colorizedR, colorizedG, colorizedB;
304 colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
307 int r = mColorizeStrength * colorizedR + ( 1 - mColorizeStrength ) * tmpColor.red();
308 int g = mColorizeStrength * colorizedG + ( 1 - mColorizeStrength ) * tmpColor.green();
309 int b = mColorizeStrength * colorizedB + ( 1 - mColorizeStrength ) * tmpColor.blue();
311 rgb = qRgba( r, g, b, qAlpha( rgb ) );
316 tmpColor.setHsl( h, s, l, qAlpha( rgb ) );
317 rgb = tmpColor.rgba();
329 else if ( factor < 1.0 )
333 QColor transparentFillColor = QColor( 0, 0, 0, 255 * factor );
334 QPainter painter( &image );
335 painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
336 painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
342 MultiplyOpacityPixelOperation operation( factor );
343 runPixelOperation( image, operation );
347 void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
351 rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), std::clamp( std::round( mFactor * qAlpha( rgb ) ), 0.0, 255.0 ) );
358 QColor opaqueColor = color;
359 opaqueColor.setAlpha( 255 );
363 QPainter painter( &image );
364 painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
365 painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
373 if ( ! properties.
ramp )
375 QgsDebugMsg( QStringLiteral(
"no color ramp specified for distance transform" ) );
380 double *array =
new double[
static_cast< qgssize >( image.width() ) * image.height()];
381 ConvertToArrayPixelOperation convertToArray( image.width(), array, properties.
shadeExterior );
382 runPixelOperation( image, convertToArray );
385 distanceTransform2d( array, image.width(), image.height() );
390 spread = std::sqrt( maxValueInDistanceTransformArray( array, image.width() * image.height() ) );
394 spread = properties.
spread;
398 ShadeFromArrayOperation shadeFromArray( image.width(), array, spread, properties );
399 runPixelOperation( image, shadeFromArray );
403 void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
408 if ( qAlpha( rgb ) > 0 )
411 mArray[ idx ] = 1 - qAlpha( rgb ) / 255.0;
422 if ( qAlpha( rgb ) == 255 )
436 void QgsImageOperation::distanceTransform1d(
double *f,
int n,
int *v,
double *z,
double *d )
442 for (
int q = 1; q <= n - 1; q++ )
444 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
448 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
457 for (
int q = 0; q <= n - 1; q++ )
459 while ( z[k + 1] < q )
461 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
465 double QgsImageOperation::maxValueInDistanceTransformArray(
const double *array,
const unsigned int size )
467 double dtMaxValue = array[0];
468 for (
unsigned int i = 1; i < size; ++i )
470 if ( array[i] > dtMaxValue )
472 dtMaxValue = array[i];
479 void QgsImageOperation::distanceTransform2d(
double *im,
int width,
int height )
481 int maxDimension = std::max( width, height );
483 double *f =
new double[ maxDimension ];
484 int *v =
new int[ maxDimension ];
485 double *z =
new double[ maxDimension + 1 ];
486 double *d =
new double[ maxDimension ];
489 for (
int x = 0; x < width; x++ )
491 for (
int y = 0; y < height; y++ )
493 f[y] = im[ x + y * width ];
495 distanceTransform1d( f, height, v, z, d );
496 for (
int y = 0; y < height; y++ )
498 im[ x + y * width ] = d[y];
503 for (
int y = 0; y < height; y++ )
505 for (
int x = 0; x < width; x++ )
507 f[x] = im[ x + y * width ];
509 distanceTransform1d( f, width, v, z, d );
510 for (
int x = 0; x < width; x++ )
512 im[ x + y * width ] = d[x];
522 void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb,
const int x,
const int y )
524 if ( ! mProperties.ramp )
529 rgb = mProperties.ramp->color( 1.0 ).rgba();
533 int idx = y * mWidth + x;
536 double squaredVal = mArray[ idx ];
537 if ( squaredVal > mSpreadSquared )
539 rgb = Qt::transparent;
543 double distance = std::sqrt( squaredVal );
544 double val = distance / mSpread;
545 QColor rampColor = mProperties.ramp->color( val );
547 if ( ( mProperties.shadeExterior && distance > mSpread - 1 ) )
550 double alphaMultiplyFactor = mSpread - distance;
551 rampColor.setAlpha( rampColor.alpha() * alphaMultiplyFactor );
553 rgb = rampColor.rgba();
561 int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
562 int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
568 QImage::Format originalFormat = image.format();
569 QImage *pImage = ℑ
570 if ( !alphaOnly && originalFormat != QImage::Format_ARGB32_Premultiplied )
572 pImage =
new QImage( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
574 else if ( alphaOnly && originalFormat != QImage::Format_ARGB32 )
576 pImage =
new QImage( image.convertToFormat( QImage::Format_ARGB32 ) );
580 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
582 StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn,
true, i1, i2 );
583 runLineOperation( *pImage, topToBottomBlur );
585 StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow,
true, i1, i2 );
586 runLineOperation( *pImage, leftToRightBlur );
588 StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn,
false, i1, i2 );
589 runLineOperation( *pImage, bottomToTopBlur );
591 StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow,
false, i1, i2 );
592 runLineOperation( *pImage, rightToLeftBlur );
594 if ( pImage->format() != originalFormat )
596 image = pImage->convertToFormat( originalFormat );
605 int width = image.width();
606 int height = image.height();
611 QImage *copy =
new QImage( image.copy() );
615 double *kernel = createGaussianKernel( radius );
618 QImage::Format originalFormat = image.format();
619 QImage *pImage = ℑ
620 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
622 pImage =
new QImage( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
626 QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
627 GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel );
628 runRectOperation( *pImage, rowBlur );
631 QImage *yBlurImage =
new QImage( width, height, QImage::Format_ARGB32_Premultiplied );
632 GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage, kernel );
633 runRectOperation( xBlurImage, colBlur );
637 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
639 QImage *convertedImage =
new QImage( yBlurImage->convertToFormat( originalFormat ) );
642 return convertedImage;
648 void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
650 int width = block.image->width();
651 int height = block.image->height();
652 int sourceBpl = block.image->bytesPerLine();
654 unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
655 QRgb *destRef =
nullptr;
656 if ( mDirection == ByRow )
658 unsigned char *sourceFirstLine = block.image->scanLine( 0 );
659 unsigned char *sourceRef;
662 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
664 sourceRef = sourceFirstLine;
665 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
666 for (
int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
668 *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
674 unsigned char *sourceRef = block.image->scanLine( block.beginLine );
675 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
677 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
678 for (
int x = 0; x < width; ++x, ++destRef )
680 *destRef = gaussianBlurHorizontal( x, sourceRef, width );
686 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical(
const int posy,
unsigned char *sourceFirstLine,
const int sourceBpl,
const int height )
695 for (
int i = 0; i <= mRadius * 2; ++i )
697 y = std::clamp( posy + ( i - mRadius ), 0, height - 1 );
698 ref = sourceFirstLine + sourceBpl * y;
700 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
701 r += mKernel[i] * qRed( *refRgb );
702 g += mKernel[i] * qGreen( *refRgb );
703 b += mKernel[i] * qBlue( *refRgb );
704 a += mKernel[i] * qAlpha( *refRgb );
707 return qRgba( r, g, b, a );
710 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal(
const int posx,
unsigned char *sourceFirstLine,
const int width )
719 for (
int i = 0; i <= mRadius * 2; ++i )
721 x = std::clamp( posx + ( i - mRadius ), 0, width - 1 );
722 ref = sourceFirstLine + x * 4;
724 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
725 r += mKernel[i] * qRed( *refRgb );
726 g += mKernel[i] * qGreen( *refRgb );
727 b += mKernel[i] * qBlue( *refRgb );
728 a += mKernel[i] * qAlpha( *refRgb );
731 return qRgba( r, g, b, a );
735 double *QgsImageOperation::createGaussianKernel(
const int radius )
737 double *kernel =
new double[ radius * 2 + 1 ];
738 double sigma = radius / 3.0;
739 double twoSigmaSquared = 2 * sigma * sigma;
740 double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
741 double expCoefficient = -1.0 / twoSigmaSquared;
745 for (
int i = 0; i <= radius; ++i )
747 result = coefficient * std::exp( i * i * expCoefficient );
748 kernel[ radius - i ] = result;
752 kernel[radius + i] = result;
757 for (
int i = 0; i <= radius * 2; ++i )
770 runLineOperation( image, flipOperation );
775 int width = image.width();
776 int height = image.height();
783 for (
int y = 0; y < height; ++y )
786 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
787 for (
int x = 0; x < width; ++x )
789 if ( qAlpha( imgScanline[x] ) )
804 for (
int y = height - 1; y >= ymin; --y )
807 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
808 for (
int x = 0; x < width; ++x )
810 if ( qAlpha( imgScanline[x] ) )
813 xmin = std::min( xmin, x );
814 xmax = std::max( xmax, x );
824 for (
int y = ymin; y <= ymax; ++y )
826 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
827 for (
int x = 0; x < xmin; ++x )
829 if ( qAlpha( imgScanline[x] ) )
838 for (
int y = ymin; y <= ymax; ++y )
840 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
841 for (
int x = width - 1; x > xmax; --x )
843 if ( qAlpha( imgScanline[x] ) )
851 if ( minSize.isValid() )
853 if ( xmax - xmin < minSize.width() )
855 xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
856 xmax = xmin + minSize.width();
858 if ( ymax - ymin < minSize.height() )
860 ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
861 ymax = ymin + minSize.height();
867 const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
868 const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
869 xmin = std::max( 0, width / 2 - dx );
870 xmax = std::min( width, width / 2 + dx );
871 ymin = std::max( 0, height / 2 - dy );
872 ymax = std::min( height, height / 2 + dy );
875 return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
883 void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef,
const int lineLength,
const int bytesPerLine )
885 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
888 unsigned char *p =
reinterpret_cast< unsigned char *
>( startRef );
889 unsigned char *tempLine =
new unsigned char[ lineLength * 4 ];
890 for (
int i = 0; i < lineLength * 4; ++i, p += increment )
892 tempLine[i++] = *( p++ );
893 tempLine[i++] = *( p++ );
894 tempLine[i++] = *( p++ );
895 tempLine[i] = *( p );
900 p =
reinterpret_cast< unsigned char *
>( startRef );
901 for (
int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
903 *( p++ ) = tempLine[i++];
904 *( p++ ) = tempLine[i++];
905 *( p++ ) = tempLine[i++];
906 *( p ) = tempLine[i];
FlipType
Flip operation types.
@ FlipHorizontal
Flip the image horizontally.
static void convertToGrayscale(QImage &image, GrayscaleMode mode=GrayscaleLuminosity)
Convert a QImage to a grayscale image.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
static void multiplyOpacity(QImage &image, double factor)
Multiplies opacity of image pixel values by a factor.
static void stackBlur(QImage &image, int radius, bool alphaOnly=false)
Performs a stack blur on an image.
static void flipImage(QImage &image, FlipType type)
Flips an image horizontally or vertically.
static void adjustHueSaturation(QImage &image, double saturation, const QColor &colorizeColor=QColor(), double colorizeStrength=1.0)
Alter the hue or saturation of a QImage.
static void distanceTransform(QImage &image, const QgsImageOperation::DistanceTransformProperties &properties)
Performs a distance transform on the source image and shades the result using a color ramp.
static QRect nonTransparentImageRect(const QImage &image, QSize minSize=QSize(), bool center=false)
Calculates the non-transparent region of an image.
static QImage cropTransparent(const QImage &image, QSize minSize=QSize(), bool center=false)
Crop any transparent border from around an image.
static void adjustBrightnessContrast(QImage &image, int brightness, double contrast)
Alter the brightness or contrast of a QImage.
GrayscaleMode
Modes for converting a QImage to grayscale.
static QImage * gaussianBlur(QImage &image, int radius)
Performs a gaussian blur on an image.
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)