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 )
205 grayscaleLuminosityOp( rgb );
208 grayscaleAverageOp( rgb );
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 qBound( 0, static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 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 ), qBound( 0.0, std::round( mFactor * qAlpha( rgb ) ), 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[ 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 )
405 int idx = y * mWidth + x;
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 );
601 void QgsImageOperation::StackBlurLineOperation::operator()( QRgb *startRef,
const int lineLength,
const int bytesPerLine )
603 unsigned char *p =
reinterpret_cast< unsigned char *
>( startRef );
605 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
606 if ( !mForwardDirection )
608 p += ( lineLength - 1 ) * increment;
609 increment = -increment;
612 for (
int i = mi1; i <= mi2; ++i )
618 for (
int j = 1; j < lineLength; ++j, p += increment )
620 for (
int i = mi1; i <= mi2; ++i )
622 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * mAlpha / 16 ) >> 4;
631 int width = image.width();
632 int height = image.height();
637 QImage *copy =
new QImage( image.copy() );
641 double *kernel = createGaussianKernel( radius );
644 QImage::Format originalFormat = image.format();
645 QImage *pImage = ℑ
646 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
648 pImage =
new QImage( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
652 QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
653 GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel );
654 runRectOperation( *pImage, rowBlur );
657 QImage *yBlurImage =
new QImage( width, height, QImage::Format_ARGB32_Premultiplied );
658 GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage, kernel );
659 runRectOperation( xBlurImage, colBlur );
663 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
665 QImage *convertedImage =
new QImage( yBlurImage->convertToFormat( originalFormat ) );
668 return convertedImage;
674 void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
676 int width = block.image->width();
677 int height = block.image->height();
678 int sourceBpl = block.image->bytesPerLine();
680 unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
681 QRgb *destRef =
nullptr;
682 if ( mDirection == ByRow )
684 unsigned char *sourceFirstLine = block.image->scanLine( 0 );
685 unsigned char *sourceRef;
688 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
690 sourceRef = sourceFirstLine;
691 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
692 for (
int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
694 *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
700 unsigned char *sourceRef = block.image->scanLine( block.beginLine );
701 for (
unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
703 destRef =
reinterpret_cast< QRgb *
>( outputLineRef );
704 for (
int x = 0; x < width; ++x, ++destRef )
706 *destRef = gaussianBlurHorizontal( x, sourceRef, width );
712 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical(
const int posy,
unsigned char *sourceFirstLine,
const int sourceBpl,
const int height )
721 for (
int i = 0; i <= mRadius * 2; ++i )
723 y = qBound( 0, posy + ( i - mRadius ), height - 1 );
724 ref = sourceFirstLine + sourceBpl * y;
726 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
727 r += mKernel[i] * qRed( *refRgb );
728 g += mKernel[i] * qGreen( *refRgb );
729 b += mKernel[i] * qBlue( *refRgb );
730 a += mKernel[i] * qAlpha( *refRgb );
733 return qRgba( r, g, b, a );
736 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal(
const int posx,
unsigned char *sourceFirstLine,
const int width )
745 for (
int i = 0; i <= mRadius * 2; ++i )
747 x = qBound( 0, posx + ( i - mRadius ), width - 1 );
748 ref = sourceFirstLine + x * 4;
750 QRgb *refRgb =
reinterpret_cast< QRgb *
>( ref );
751 r += mKernel[i] * qRed( *refRgb );
752 g += mKernel[i] * qGreen( *refRgb );
753 b += mKernel[i] * qBlue( *refRgb );
754 a += mKernel[i] * qAlpha( *refRgb );
757 return qRgba( r, g, b, a );
761 double *QgsImageOperation::createGaussianKernel(
const int radius )
763 double *kernel =
new double[ radius * 2 + 1 ];
764 double sigma = radius / 3.0;
765 double twoSigmaSquared = 2 * sigma * sigma;
766 double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
767 double expCoefficient = -1.0 / twoSigmaSquared;
771 for (
int i = 0; i <= radius; ++i )
773 result = coefficient * std::exp( i * i * expCoefficient );
774 kernel[ radius - i ] = result;
778 kernel[radius + i] = result;
783 for (
int i = 0; i <= radius * 2; ++i )
796 runLineOperation( image, flipOperation );
801 int width = image.width();
802 int height = image.height();
809 for (
int y = 0; y < height; ++y )
812 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
813 for (
int x = 0; x < width; ++x )
815 if ( qAlpha( imgScanline[x] ) )
830 for (
int y = height - 1; y >= ymin; --y )
833 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
834 for (
int x = 0; x < width; ++x )
836 if ( qAlpha( imgScanline[x] ) )
839 xmin = std::min( xmin, x );
840 xmax = std::max( xmax, x );
850 for (
int y = ymin; y <= ymax; ++y )
852 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
853 for (
int x = 0; x < xmin; ++x )
855 if ( qAlpha( imgScanline[x] ) )
863 for (
int y = ymin; y <= ymax; ++y )
865 const QRgb *imgScanline =
reinterpret_cast< const QRgb *
>( image.constScanLine( y ) );
866 for (
int x = width - 1; x > xmax; --x )
868 if ( qAlpha( imgScanline[x] ) )
875 if ( minSize.isValid() )
877 if ( xmax - xmin < minSize.width() )
879 xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
880 xmax = xmin + minSize.width();
882 if ( ymax - ymin < minSize.height() )
884 ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
885 ymax = ymin + minSize.height();
891 const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
892 const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
893 xmin = std::max( 0, width / 2 - dx );
894 xmax = std::min( width, width / 2 + dx );
895 ymin = std::max( 0, height / 2 - dy );
896 ymax = std::min( height, height / 2 + dy );
899 return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
907 void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef,
const int lineLength,
const int bytesPerLine )
909 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
912 unsigned char *p =
reinterpret_cast< unsigned char *
>( startRef );
913 unsigned char *tempLine =
new unsigned char[ lineLength * 4 ];
914 for (
int i = 0; i < lineLength * 4; ++i, p += increment )
916 tempLine[i++] = *( p++ );
917 tempLine[i++] = *( p++ );
918 tempLine[i++] = *( p++ );
919 tempLine[i] = *( p );
924 p =
reinterpret_cast< unsigned char *
>( startRef );
925 for (
int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
927 *( p++ ) = tempLine[i++];
928 *( p++ ) = tempLine[i++];
929 *( p++ ) = tempLine[i++];
930 *( p ) = tempLine[i];
static void multiplyOpacity(QImage &image, double factor)
Multiplies opacity of image pixel values by a factor.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
Flip the image horizontally.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Keep the lightness of the color, drops the saturation.
static void convertToGrayscale(QImage &image, GrayscaleMode mode=GrayscaleLuminosity)
Convert a QImage to a grayscale image.
FlipType
Flip operation types.
Grayscale by perceptual luminosity (weighted sum of color RGB components)
Grayscale by taking average of color RGB components.
static QImage cropTransparent(const QImage &image, QSize minSize=QSize(), bool center=false)
Crop any transparent border from around an image.
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 flipImage(QImage &image, FlipType type)
Flips an image horizontally or vertically.
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 QImage * gaussianBlur(QImage &image, int radius)
Performs a gaussian blur on an image.
static void stackBlur(QImage &image, int radius, bool alphaOnly=false)
Performs a stack blur on an image.
static QRect nonTransparentImageRect(const QImage &image, QSize minSize=QSize(), bool center=false)
Calculates the non-transparent region of an image.
GrayscaleMode
Modes for converting a QImage to grayscale.
static void adjustBrightnessContrast(QImage &image, int brightness, double contrast)
Alter the brightness or contrast of a QImage.