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( QString(
"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 overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
Flip the image horizontally.
static void multiplyOpacity(QImage &image, const double factor)
Multiplies opacity of image pixel values by a factor.
static void convertToGrayscale(QImage &image, const GrayscaleMode mode=GrayscaleLuminosity)
Convert a QImage to a grayscale image.
Keep the lightness of the color, drops the saturation.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
FlipType
Flip operation types.
Grayscale by perceptual luminosity (weighted sum of color RGB components)
Grayscale by taking average of color RGB components.
static void adjustBrightnessContrast(QImage &image, const int brightness, const double contrast)
Alter the brightness or contrast of a QImage.
static QImage * gaussianBlur(QImage &image, const int radius)
Performs a gaussian 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 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 void adjustHueSaturation(QImage &image, const double saturation, const QColor &colorizeColor=QColor(), const double colorizeStrength=1.0)
Alter the hue or saturation of a QImage.
static void stackBlur(QImage &image, const int radius, const 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.