23 #include <QtConcurrentMap>
29 #define BLOCK_THREADS 16
35 template <
typename PixelOperation>
36 void 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 );
52 template <
typename PixelOperation>
53 void 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 );
72 template <
typename RectOperation>
73 void 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 );
89 template <
class RectOperation>
90 void 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 );
103 template <
typename LineOperation>
104 void 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() );
121 template <
class LineOperation>
122 void 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 );
157 template <
typename BlockOperation>
158 void 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 );
207 void 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 );
228 void 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 ) );
241 void 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 ) );
247 void 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 );
263 void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
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 ) );
273 int 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 );
288 void 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 );
348 QPainter painter( &image );
349 painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
350 painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
357 MultiplyOpacityPixelOperation operation( factor );
358 runPixelOperation( image, operation, feedback );
362 void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
366 rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), std::clamp( std::round( mFactor * qAlpha( rgb ) ), 0.0, 255.0 ) );
373 QColor opaqueColor = color;
374 opaqueColor.setAlpha( 255 );
379 QPainter painter( &image );
380 painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
381 painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
389 if ( ! properties.
ramp )
391 QgsDebugMsg( QStringLiteral(
"no color ramp specified for distance transform" ) );
396 std::unique_ptr<double[]> array(
new double[
static_cast< qgssize >( image.width() ) * image.height()] );
401 ConvertToArrayPixelOperation convertToArray( image.width(), array.get(), properties.
shadeExterior );
402 runPixelOperation( image, convertToArray, feedback );
407 distanceTransform2d( array.get(), image.width(), image.height(), feedback );
414 spread = std::sqrt( maxValueInDistanceTransformArray( array.get(), image.width() * image.height() ) );
418 spread = properties.
spread;
425 ShadeFromArrayOperation shadeFromArray( image.width(), array.get(), spread, properties );
426 runPixelOperation( image, shadeFromArray, feedback );
429 void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb,
const int x,
const int y )
434 if ( qAlpha( rgb ) > 0 )
437 mArray[ idx ] = 1 - qAlpha( rgb ) / 255.0;
448 if ( qAlpha( rgb ) == 255 )
462 void QgsImageOperation::distanceTransform1d(
double *f,
int n,
int *v,
double *z,
double *d )
468 for (
int q = 1; q <= n - 1; q++ )
470 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
474 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
483 for (
int q = 0; q <= n - 1; q++ )
485 while ( z[k + 1] < q )
487 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
491 double QgsImageOperation::maxValueInDistanceTransformArray(
const double *array,
const unsigned int size )
493 double dtMaxValue = array[0];
494 for (
unsigned int i = 1; i < size; ++i )
496 if ( array[i] > dtMaxValue )
498 dtMaxValue = array[i];
505 void QgsImageOperation::distanceTransform2d(
double *im,
int width,
int height,
QgsFeedback *feedback )
507 int maxDimension = std::max( width, height );
509 std::unique_ptr<double[]> f(
new double[ maxDimension ] );
510 std::unique_ptr<int []> v(
new int[ maxDimension ] );
511 std::unique_ptr<double[]>z(
new double[ maxDimension + 1 ] );
512 std::unique_ptr<double[]>d(
new double[ maxDimension ] );
515 for (
int x = 0; x < width; x++ )
520 for (
int y = 0; y < height; y++ )
522 f[y] = im[ x + y * width ];
524 distanceTransform1d( f.get(), height, v.get(), z.get(), d.get() );
525 for (
int y = 0; y < height; y++ )
527 im[ x + y * width ] = d[y];
532 for (
int y = 0; y < height; y++ )
537 for (
int x = 0; x < width; x++ )
539 f[x] = im[ x + y * width ];
541 distanceTransform1d( f.get(), width, v.get(), z.get(), d.get() );
542 for (
int x = 0; x < width; x++ )
544 im[ x + y * width ] = d[x];
549 void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb,
const int x,
const int y )
551 if ( ! mProperties.ramp )
556 rgb = mProperties.ramp->color( 1.0 ).rgba();
560 int idx = y * mWidth + x;
563 double squaredVal = mArray[ idx ];
564 if ( squaredVal > mSpreadSquared )
566 rgb = Qt::transparent;
570 double distance = std::sqrt( squaredVal );
571 double val = distance / mSpread;
572 QColor rampColor = mProperties.ramp->color( val );
574 if ( ( mProperties.shadeExterior && distance > mSpread - 1 ) )
577 double alphaMultiplyFactor = mSpread - distance;
578 rampColor.setAlpha( rampColor.alpha() * alphaMultiplyFactor );
580 rgb = rampColor.rgba();
588 int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
589 int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
595 QImage::Format originalFormat = image.format();
596 QImage *pImage = ℑ
597 std::unique_ptr< QImage> convertedImage;
598 if ( !alphaOnly && originalFormat != QImage::Format_ARGB32_Premultiplied )
600 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
601 pImage = convertedImage.get();
603 else if ( alphaOnly && originalFormat != QImage::Format_ARGB32 )
605 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32 ) );
606 pImage = convertedImage.get();
617 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
619 StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn,
true, i1, i2, feedback );
620 runLineOperation( *pImage, topToBottomBlur, feedback );
625 StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow,
true, i1, i2, feedback );
626 runLineOperation( *pImage, leftToRightBlur, feedback );
631 StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn,
false, i1, i2, feedback );
632 runLineOperation( *pImage, bottomToTopBlur, feedback );
637 StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow,
false, i1, i2, feedback );
638 runLineOperation( *pImage, rightToLeftBlur, feedback );
643 if ( pImage->format() != originalFormat )
645 image = pImage->convertToFormat( originalFormat );
653 int width = image.width();
654 int height = image.height();
659 QImage *copy =
new QImage( image.copy() );
663 std::unique_ptr<double[]>kernel( createGaussianKernel( radius ) );
668 QImage::Format originalFormat = image.format();
669 QImage *pImage = ℑ
670 std::unique_ptr< QImage> convertedImage;
671 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
673 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
674 pImage = convertedImage.get();
684 QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
685 GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel.get(), feedback );
686 runRectOperation( *pImage, rowBlur );
692 std::unique_ptr< QImage > yBlurImage = std::make_unique< QImage >( width, height, QImage::Format_ARGB32_Premultiplied );
693 GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage.get(), kernel.get(), feedback );
694 runRectOperation( xBlurImage, colBlur );
701 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
703 return new QImage( yBlurImage->convertToFormat( originalFormat ) );
706 return yBlurImage.release();
709 void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
711 if ( mFeedback && mFeedback->isCanceled() )
714 int width = block.image->width();
715 int height = block.image->height();
716 int sourceBpl = block.image->bytesPerLine();
718 unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
719 QRgb *destRef =
nullptr;
720 if ( mDirection == ByRow )
722 unsigned char *sourceFirstLine = block.image->scanLine( 0 );
723 unsigned char *sourceRef;
726 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
728 if ( mFeedback && mFeedback->isCanceled() )
731 sourceRef = sourceFirstLine;
732 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
733 for (
int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
735 if ( mFeedback && mFeedback->isCanceled() )
738 *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
744 unsigned char *sourceRef = block.image->scanLine( block.beginLine );
745 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
747 if ( mFeedback && mFeedback->isCanceled() )
750 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
751 for (
int x = 0; x < width; ++x, ++destRef )
753 if ( mFeedback && mFeedback->isCanceled() )
756 *destRef = gaussianBlurHorizontal( x, sourceRef, width );
762 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical(
const int posy,
unsigned char *sourceFirstLine,
const int sourceBpl,
const int height )
const
771 for (
int i = 0; i <= mRadius * 2; ++i )
773 y = std::clamp( posy + ( i - mRadius ), 0, height - 1 );
774 ref = sourceFirstLine + sourceBpl * y;
776 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
777 r += mKernel[i] * qRed( *refRgb );
778 g += mKernel[i] * qGreen( *refRgb );
779 b += mKernel[i] * qBlue( *refRgb );
780 a += mKernel[i] * qAlpha( *refRgb );
783 return qRgba( r, g, b, a );
786 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal(
const int posx,
unsigned char *sourceFirstLine,
const int width )
const
795 for (
int i = 0; i <= mRadius * 2; ++i )
797 x = std::clamp( posx + ( i - mRadius ), 0, width - 1 );
798 ref = sourceFirstLine + x * 4;
800 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
801 r += mKernel[i] * qRed( *refRgb );
802 g += mKernel[i] * qGreen( *refRgb );
803 b += mKernel[i] * qBlue( *refRgb );
804 a += mKernel[i] * qAlpha( *refRgb );
807 return qRgba( r, g, b, a );
811 double *QgsImageOperation::createGaussianKernel(
const int radius )
813 double *kernel =
new double[ radius * 2 + 1 ];
814 double sigma = radius / 3.0;
815 double twoSigmaSquared = 2 * sigma * sigma;
816 double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
817 double expCoefficient = -1.0 / twoSigmaSquared;
821 for (
int i = 0; i <= radius; ++i )
823 result = coefficient * std::exp( i * i * expCoefficient );
824 kernel[ radius - i ] = result;
828 kernel[radius + i] = result;
833 for (
int i = 0; i <= radius * 2; ++i )
847 runLineOperation( image, flipOperation );
852 int width = image.width();
853 int height = image.height();
860 for (
int y = 0; y < height; ++y )
863 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
864 for (
int x = 0; x < width; ++x )
866 if ( qAlpha( imgScanline[x] ) )
881 for (
int y = height - 1; y >= ymin; --y )
884 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
885 for (
int x = 0; x < width; ++x )
887 if ( qAlpha( imgScanline[x] ) )
890 xmin = std::min( xmin, x );
891 xmax = std::max( xmax, x );
901 for (
int y = ymin; y <= ymax; ++y )
903 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
904 for (
int x = 0; x < xmin; ++x )
906 if ( qAlpha( imgScanline[x] ) )
915 for (
int y = ymin; y <= ymax; ++y )
917 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
918 for (
int x = width - 1; x > xmax; --x )
920 if ( qAlpha( imgScanline[x] ) )
928 if ( minSize.isValid() )
930 if ( xmax - xmin < minSize.width() )
932 xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
933 xmax = xmin + minSize.width();
935 if ( ymax - ymin < minSize.height() )
937 ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
938 ymax = ymin + minSize.height();
944 const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
945 const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
946 xmin = std::max( 0, width / 2 - dx );
947 xmax = std::min( width, width / 2 + dx );
948 ymin = std::max( 0, height / 2 - dy );
949 ymax = std::min( height, height / 2 + dy );
952 return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
960 void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef,
const int lineLength,
const int bytesPerLine )
const
962 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
965 unsigned char *p =
reinterpret_cast< unsigned char *
>( startRef );
966 unsigned char *tempLine =
new unsigned char[ lineLength * 4 ];
967 for (
int i = 0; i < lineLength * 4; ++i, p += increment )
969 tempLine[i++] = *( p++ );
970 tempLine[i++] = *( p++ );
971 tempLine[i++] = *( p++ );
972 tempLine[i] = *( p );
977 p =
reinterpret_cast< unsigned char *
>( startRef );
978 for (
int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
980 *( p++ ) = tempLine[i++];
981 *( p++ ) = tempLine[i++];
982 *( p++ ) = tempLine[i++];
983 *( p ) = tempLine[i];