Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsimageoperation.cpp
3 : : ----------------------
4 : : begin : January 2015
5 : : copyright : (C) 2015 by Nyall Dawson
6 : : email : nyall.dawson@gmail.com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgsimageoperation.h"
19 : : #include "qgis.h"
20 : : #include "qgscolorramp.h"
21 : : #include "qgslogger.h"
22 : : #include <QtConcurrentMap>
23 : : #include <QColor>
24 : : #include <QPainter>
25 : :
26 : : //determined via trial-and-error. Could possibly be optimised, or varied
27 : : //depending on the image size.
28 : : #define BLOCK_THREADS 16
29 : :
30 : : #define INF 1E20
31 : :
32 : : /// @cond PRIVATE
33 : :
34 : : template <typename PixelOperation>
35 : 0 : void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation )
36 : : {
37 : 0 : if ( image.height() * image.width() < 100000 )
38 : : {
39 : : //small image, don't multithread
40 : : //this threshold was determined via testing various images
41 : 0 : runPixelOperationOnWholeImage( image, operation );
42 : 0 : }
43 : : else
44 : : {
45 : : //large image, multithread operation
46 : 0 : QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation );
47 : 0 : runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
48 : : }
49 : 0 : }
50 : :
51 : : template <typename PixelOperation>
52 : 0 : void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation )
53 : : {
54 : 0 : int height = image.height();
55 : 0 : int width = image.width();
56 : 0 : for ( int y = 0; y < height; ++y )
57 : : {
58 : 0 : QRgb *ref = reinterpret_cast< QRgb * >( image.scanLine( y ) );
59 : 0 : for ( int x = 0; x < width; ++x )
60 : : {
61 : 0 : operation( ref[x], x, y );
62 : 0 : }
63 : 0 : }
64 : 0 : }
65 : :
66 : : //rect operations
67 : :
68 : : template <typename RectOperation>
69 : 0 : void QgsImageOperation::runRectOperation( QImage &image, RectOperation &operation )
70 : : {
71 : : //possibly could be tweaked for rect operations
72 : 0 : if ( image.height() * image.width() < 100000 )
73 : : {
74 : : //small image, don't multithread
75 : : //this threshold was determined via testing various images
76 : 0 : runRectOperationOnWholeImage( image, operation );
77 : 0 : }
78 : : else
79 : : {
80 : : //large image, multithread operation
81 : 0 : runBlockOperationInThreads( image, operation, ByRow );
82 : : }
83 : 0 : }
84 : :
85 : : template <class RectOperation>
86 : 0 : void QgsImageOperation::runRectOperationOnWholeImage( QImage &image, RectOperation &operation )
87 : : {
88 : 0 : ImageBlock fullImage;
89 : 0 : fullImage.beginLine = 0;
90 : 0 : fullImage.endLine = image.height();
91 : 0 : fullImage.lineLength = image.width();
92 : 0 : fullImage.image = ℑ
93 : :
94 : 0 : operation( fullImage );
95 : 0 : }
96 : :
97 : : //linear operations
98 : :
99 : : template <typename LineOperation>
100 : 0 : void QgsImageOperation::runLineOperation( QImage &image, LineOperation &operation )
101 : : {
102 : : //possibly could be tweaked for rect operations
103 : 0 : if ( image.height() * image.width() < 100000 )
104 : : {
105 : : //small image, don't multithread
106 : : //this threshold was determined via testing various images
107 : 0 : runLineOperationOnWholeImage( image, operation );
108 : 0 : }
109 : : else
110 : : {
111 : : //large image, multithread operation
112 : 0 : QgsImageOperation::ProcessBlockUsingLineOperation<LineOperation> blockOp( operation );
113 : 0 : runBlockOperationInThreads( image, blockOp, operation.direction() );
114 : : }
115 : 0 : }
116 : :
117 : : template <class LineOperation>
118 : 0 : void QgsImageOperation::runLineOperationOnWholeImage( QImage &image, LineOperation &operation )
119 : : {
120 : 0 : int height = image.height();
121 : 0 : int width = image.width();
122 : :
123 : : //do something with whole lines
124 : 0 : int bpl = image.bytesPerLine();
125 : 0 : if ( operation.direction() == ByRow )
126 : : {
127 : 0 : for ( int y = 0; y < height; ++y )
128 : : {
129 : 0 : QRgb *ref = reinterpret_cast< QRgb * >( image.scanLine( y ) );
130 : 0 : operation( ref, width, bpl );
131 : 0 : }
132 : 0 : }
133 : : else
134 : : {
135 : : //by column
136 : 0 : unsigned char *ref = image.scanLine( 0 );
137 : 0 : for ( int x = 0; x < width; ++x, ref += 4 )
138 : : {
139 : 0 : operation( reinterpret_cast< QRgb * >( ref ), height, bpl );
140 : 0 : }
141 : : }
142 : 0 : }
143 : :
144 : :
145 : : //multithreaded block processing
146 : :
147 : : template <typename BlockOperation>
148 : 0 : void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction )
149 : : {
150 : 0 : QList< ImageBlock > blocks;
151 : 0 : unsigned int height = image.height();
152 : 0 : unsigned int width = image.width();
153 : :
154 : 0 : unsigned int blockDimension1 = ( direction == QgsImageOperation::ByRow ) ? height : width;
155 : 0 : unsigned int blockDimension2 = ( direction == QgsImageOperation::ByRow ) ? width : height;
156 : :
157 : : //chunk image up into vertical blocks
158 : 0 : blocks.reserve( BLOCK_THREADS );
159 : 0 : unsigned int begin = 0;
160 : 0 : unsigned int blockLen = blockDimension1 / BLOCK_THREADS;
161 : 0 : for ( unsigned int block = 0; block < BLOCK_THREADS; ++block, begin += blockLen )
162 : : {
163 : 0 : ImageBlock newBlock;
164 : 0 : newBlock.beginLine = begin;
165 : : //make sure last block goes to end of image
166 : 0 : newBlock.endLine = block < ( BLOCK_THREADS - 1 ) ? begin + blockLen : blockDimension1;
167 : 0 : newBlock.lineLength = blockDimension2;
168 : 0 : newBlock.image = ℑ
169 : 0 : blocks << newBlock;
170 : 0 : }
171 : :
172 : : //process blocks
173 : 0 : QtConcurrent::blockingMap( blocks, operation );
174 : 0 : }
175 : :
176 : :
177 : : ///@endcond
178 : :
179 : : //
180 : : //operation specific code
181 : : //
182 : :
183 : : //grayscale
184 : :
185 : 0 : void QgsImageOperation::convertToGrayscale( QImage &image, const GrayscaleMode mode )
186 : : {
187 : 0 : if ( mode == GrayscaleOff )
188 : : {
189 : 0 : return;
190 : : }
191 : :
192 : 0 : GrayscalePixelOperation operation( mode );
193 : 0 : runPixelOperation( image, operation );
194 : 0 : }
195 : :
196 : 0 : void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb, const int x, const int y )
197 : : {
198 : : Q_UNUSED( x )
199 : : Q_UNUSED( y )
200 : 0 : switch ( mMode )
201 : : {
202 : : case GrayscaleOff:
203 : 0 : return;
204 : : case GrayscaleLuminosity:
205 : 0 : grayscaleLuminosityOp( rgb );
206 : 0 : return;
207 : : case GrayscaleAverage:
208 : 0 : grayscaleAverageOp( rgb );
209 : 0 : return;
210 : : case GrayscaleLightness:
211 : : default:
212 : 0 : grayscaleLightnessOp( rgb );
213 : 0 : return;
214 : : }
215 : 0 : }
216 : :
217 : 0 : void QgsImageOperation::grayscaleLightnessOp( QRgb &rgb )
218 : : {
219 : 0 : int red = qRed( rgb );
220 : 0 : int green = qGreen( rgb );
221 : 0 : int blue = qBlue( rgb );
222 : :
223 : 0 : int min = std::min( std::min( red, green ), blue );
224 : 0 : int max = std::max( std::max( red, green ), blue );
225 : :
226 : 0 : int lightness = std::min( ( min + max ) / 2, 255 );
227 : 0 : rgb = qRgba( lightness, lightness, lightness, qAlpha( rgb ) );
228 : 0 : }
229 : :
230 : 0 : void QgsImageOperation::grayscaleLuminosityOp( QRgb &rgb )
231 : : {
232 : 0 : int luminosity = 0.21 * qRed( rgb ) + 0.72 * qGreen( rgb ) + 0.07 * qBlue( rgb );
233 : 0 : rgb = qRgba( luminosity, luminosity, luminosity, qAlpha( rgb ) );
234 : 0 : }
235 : :
236 : 0 : void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )
237 : : {
238 : 0 : int average = ( qRed( rgb ) + qGreen( rgb ) + qBlue( rgb ) ) / 3;
239 : 0 : rgb = qRgba( average, average, average, qAlpha( rgb ) );
240 : 0 : }
241 : :
242 : :
243 : : //brightness/contrast
244 : :
245 : 0 : void QgsImageOperation::adjustBrightnessContrast( QImage &image, const int brightness, const double contrast )
246 : : {
247 : 0 : BrightnessContrastPixelOperation operation( brightness, contrast );
248 : 0 : runPixelOperation( image, operation );
249 : 0 : }
250 : :
251 : 0 : void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb, const int x, const int y )
252 : : {
253 : : Q_UNUSED( x )
254 : : Q_UNUSED( y )
255 : 0 : int red = adjustColorComponent( qRed( rgb ), mBrightness, mContrast );
256 : 0 : int blue = adjustColorComponent( qBlue( rgb ), mBrightness, mContrast );
257 : 0 : int green = adjustColorComponent( qGreen( rgb ), mBrightness, mContrast );
258 : 0 : rgb = qRgba( red, green, blue, qAlpha( rgb ) );
259 : 0 : }
260 : :
261 : 0 : int QgsImageOperation::adjustColorComponent( int colorComponent, int brightness, double contrastFactor )
262 : : {
263 : 0 : return std::clamp( static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 0, 255 );
264 : : }
265 : :
266 : : //hue/saturation
267 : :
268 : 0 : void QgsImageOperation::adjustHueSaturation( QImage &image, const double saturation, const QColor &colorizeColor, const double colorizeStrength )
269 : : {
270 : 0 : HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0,
271 : 0 : colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
272 : 0 : runPixelOperation( image, operation );
273 : 0 : }
274 : :
275 : 0 : void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb, const int x, const int y )
276 : : {
277 : : Q_UNUSED( x )
278 : : Q_UNUSED( y )
279 : 0 : QColor tmpColor( rgb );
280 : : int h, s, l;
281 : 0 : tmpColor.getHsl( &h, &s, &l );
282 : :
283 : 0 : if ( mSaturation < 1.0 )
284 : : {
285 : : // Lowering the saturation. Use a simple linear relationship
286 : 0 : s = std::min( static_cast< int >( s * mSaturation ), 255 );
287 : 0 : }
288 : 0 : else if ( mSaturation > 1.0 )
289 : : {
290 : : // Raising the saturation. Use a saturation curve to prevent
291 : : // clipping at maximum saturation with ugly results.
292 : 0 : s = std::min( static_cast< int >( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::pow( mSaturation, 2 ) ) ) ), 255 );
293 : 0 : }
294 : :
295 : 0 : if ( mColorize )
296 : : {
297 : 0 : h = mColorizeHue;
298 : 0 : s = mColorizeSaturation;
299 : 0 : if ( mColorizeStrength < 1.0 )
300 : : {
301 : : //get rgb for colorized color
302 : 0 : QColor colorizedColor = QColor::fromHsl( h, s, l );
303 : : int colorizedR, colorizedG, colorizedB;
304 : 0 : colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
305 : :
306 : : // Now, linearly scale by colorize strength
307 : 0 : int r = mColorizeStrength * colorizedR + ( 1 - mColorizeStrength ) * tmpColor.red();
308 : 0 : int g = mColorizeStrength * colorizedG + ( 1 - mColorizeStrength ) * tmpColor.green();
309 : 0 : int b = mColorizeStrength * colorizedB + ( 1 - mColorizeStrength ) * tmpColor.blue();
310 : :
311 : 0 : rgb = qRgba( r, g, b, qAlpha( rgb ) );
312 : 0 : return;
313 : : }
314 : 0 : }
315 : :
316 : 0 : tmpColor.setHsl( h, s, l, qAlpha( rgb ) );
317 : 0 : rgb = tmpColor.rgba();
318 : 0 : }
319 : :
320 : : //multiply opacity
321 : :
322 : 0 : void QgsImageOperation::multiplyOpacity( QImage &image, const double factor )
323 : : {
324 : 0 : if ( qgsDoubleNear( factor, 1.0 ) )
325 : : {
326 : : //no change
327 : 0 : return;
328 : : }
329 : 0 : else if ( factor < 1.0 )
330 : : {
331 : : //decreasing opacity - we can use the faster DestinationIn composition mode
332 : : //to reduce the alpha channel
333 : 0 : QColor transparentFillColor = QColor( 0, 0, 0, 255 * factor );
334 : 0 : QPainter painter( &image );
335 : 0 : painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
336 : 0 : painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
337 : 0 : painter.end();
338 : 0 : }
339 : : else
340 : : {
341 : : //increasing opacity - run this as a pixel operation for multithreading
342 : 0 : MultiplyOpacityPixelOperation operation( factor );
343 : 0 : runPixelOperation( image, operation );
344 : : }
345 : 0 : }
346 : :
347 : 0 : void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb, const int x, const int y )
348 : : {
349 : : Q_UNUSED( x )
350 : : Q_UNUSED( y )
351 : 0 : rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), std::clamp( std::round( mFactor * qAlpha( rgb ) ), 0.0, 255.0 ) );
352 : 0 : }
353 : :
354 : : // overlay color
355 : :
356 : 0 : void QgsImageOperation::overlayColor( QImage &image, const QColor &color )
357 : : {
358 : 0 : QColor opaqueColor = color;
359 : 0 : opaqueColor.setAlpha( 255 );
360 : :
361 : : //use QPainter SourceIn composition mode to overlay color (fast)
362 : : //this retains image's alpha channel but replaces color
363 : 0 : QPainter painter( &image );
364 : 0 : painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
365 : 0 : painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
366 : 0 : painter.end();
367 : 0 : }
368 : :
369 : : // distance transform
370 : :
371 : 0 : void QgsImageOperation::distanceTransform( QImage &image, const DistanceTransformProperties &properties )
372 : : {
373 : 0 : if ( ! properties.ramp )
374 : : {
375 : 0 : QgsDebugMsg( QStringLiteral( "no color ramp specified for distance transform" ) );
376 : 0 : return;
377 : : }
378 : :
379 : : //first convert to 1 bit alpha mask array
380 : 0 : double *array = new double[ static_cast< qgssize >( image.width() ) * image.height()];
381 : 0 : ConvertToArrayPixelOperation convertToArray( image.width(), array, properties.shadeExterior );
382 : 0 : runPixelOperation( image, convertToArray );
383 : :
384 : : //calculate distance transform (single threaded only)
385 : 0 : distanceTransform2d( array, image.width(), image.height() );
386 : :
387 : : double spread;
388 : 0 : if ( properties.useMaxDistance )
389 : : {
390 : 0 : spread = std::sqrt( maxValueInDistanceTransformArray( array, image.width() * image.height() ) );
391 : 0 : }
392 : : else
393 : : {
394 : 0 : spread = properties.spread;
395 : : }
396 : :
397 : : //shade distance transform
398 : 0 : ShadeFromArrayOperation shadeFromArray( image.width(), array, spread, properties );
399 : 0 : runPixelOperation( image, shadeFromArray );
400 : 0 : delete [] array;
401 : 0 : }
402 : :
403 : 0 : void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb, const int x, const int y )
404 : : {
405 : 0 : qgssize idx = y * static_cast< qgssize >( mWidth ) + x;
406 : 0 : if ( mExterior )
407 : : {
408 : 0 : if ( qAlpha( rgb ) > 0 )
409 : : {
410 : : //opaque pixel, so zero distance
411 : 0 : mArray[ idx ] = 1 - qAlpha( rgb ) / 255.0;
412 : 0 : }
413 : : else
414 : : {
415 : : //transparent pixel, so initially set distance as infinite
416 : 0 : mArray[ idx ] = INF;
417 : : }
418 : 0 : }
419 : : else
420 : : {
421 : : //TODO - fix this for semi-transparent pixels
422 : 0 : if ( qAlpha( rgb ) == 255 )
423 : : {
424 : 0 : mArray[ idx ] = INF;
425 : 0 : }
426 : : else
427 : : {
428 : 0 : mArray[idx] = 0;
429 : : }
430 : : }
431 : 0 : }
432 : :
433 : : //fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
434 : :
435 : : /* distance transform of a 1d function using squared distance */
436 : 0 : void QgsImageOperation::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
437 : : {
438 : 0 : int k = 0;
439 : 0 : v[0] = 0;
440 : 0 : z[0] = -INF;
441 : 0 : z[1] = + INF;
442 : 0 : for ( int q = 1; q <= n - 1; q++ )
443 : : {
444 : 0 : double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
445 : 0 : while ( s <= z[k] )
446 : : {
447 : 0 : k--;
448 : 0 : s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
449 : : }
450 : 0 : k++;
451 : 0 : v[k] = q;
452 : 0 : z[k] = s;
453 : 0 : z[k + 1] = + INF;
454 : 0 : }
455 : :
456 : 0 : k = 0;
457 : 0 : for ( int q = 0; q <= n - 1; q++ )
458 : : {
459 : 0 : while ( z[k + 1] < q )
460 : 0 : k++;
461 : 0 : d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
462 : 0 : }
463 : 0 : }
464 : :
465 : 0 : double QgsImageOperation::maxValueInDistanceTransformArray( const double *array, const unsigned int size )
466 : : {
467 : 0 : double dtMaxValue = array[0];
468 : 0 : for ( unsigned int i = 1; i < size; ++i )
469 : : {
470 : 0 : if ( array[i] > dtMaxValue )
471 : : {
472 : 0 : dtMaxValue = array[i];
473 : 0 : }
474 : 0 : }
475 : 0 : return dtMaxValue;
476 : : }
477 : :
478 : : /* distance transform of 2d function using squared distance */
479 : 0 : void QgsImageOperation::distanceTransform2d( double *im, int width, int height )
480 : : {
481 : 0 : int maxDimension = std::max( width, height );
482 : :
483 : 0 : double *f = new double[ maxDimension ];
484 : 0 : int *v = new int[ maxDimension ];
485 : 0 : double *z = new double[ maxDimension + 1 ];
486 : 0 : double *d = new double[ maxDimension ];
487 : :
488 : : // transform along columns
489 : 0 : for ( int x = 0; x < width; x++ )
490 : : {
491 : 0 : for ( int y = 0; y < height; y++ )
492 : : {
493 : 0 : f[y] = im[ x + y * width ];
494 : 0 : }
495 : 0 : distanceTransform1d( f, height, v, z, d );
496 : 0 : for ( int y = 0; y < height; y++ )
497 : : {
498 : 0 : im[ x + y * width ] = d[y];
499 : 0 : }
500 : 0 : }
501 : :
502 : : // transform along rows
503 : 0 : for ( int y = 0; y < height; y++ )
504 : : {
505 : 0 : for ( int x = 0; x < width; x++ )
506 : : {
507 : 0 : f[x] = im[ x + y * width ];
508 : 0 : }
509 : 0 : distanceTransform1d( f, width, v, z, d );
510 : 0 : for ( int x = 0; x < width; x++ )
511 : : {
512 : 0 : im[ x + y * width ] = d[x];
513 : 0 : }
514 : 0 : }
515 : :
516 : 0 : delete [] d;
517 : 0 : delete [] f;
518 : 0 : delete [] v;
519 : 0 : delete [] z;
520 : 0 : }
521 : :
522 : 0 : void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb, const int x, const int y )
523 : : {
524 : 0 : if ( ! mProperties.ramp )
525 : 0 : return;
526 : :
527 : 0 : if ( qgsDoubleNear( mSpread, 0.0 ) )
528 : : {
529 : 0 : rgb = mProperties.ramp->color( 1.0 ).rgba();
530 : 0 : return;
531 : : }
532 : :
533 : 0 : int idx = y * mWidth + x;
534 : :
535 : : //values are distance squared
536 : 0 : double squaredVal = mArray[ idx ];
537 : 0 : if ( squaredVal > mSpreadSquared )
538 : : {
539 : 0 : rgb = Qt::transparent;
540 : 0 : return;
541 : : }
542 : :
543 : 0 : double distance = std::sqrt( squaredVal );
544 : 0 : double val = distance / mSpread;
545 : 0 : QColor rampColor = mProperties.ramp->color( val );
546 : :
547 : 0 : if ( ( mProperties.shadeExterior && distance > mSpread - 1 ) )
548 : : {
549 : : //fade off final pixel to antialias edge
550 : 0 : double alphaMultiplyFactor = mSpread - distance;
551 : 0 : rampColor.setAlpha( rampColor.alpha() * alphaMultiplyFactor );
552 : 0 : }
553 : 0 : rgb = rampColor.rgba();
554 : 0 : }
555 : :
556 : : //stack blur
557 : :
558 : 0 : void QgsImageOperation::stackBlur( QImage &image, const int radius, const bool alphaOnly )
559 : : {
560 : : // culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
561 : 0 : int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
562 : 0 : int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
563 : :
564 : 0 : int i1 = 0;
565 : 0 : int i2 = 3;
566 : :
567 : : //ensure correct source format.
568 : 0 : QImage::Format originalFormat = image.format();
569 : 0 : QImage *pImage = ℑ
570 : 0 : if ( !alphaOnly && originalFormat != QImage::Format_ARGB32_Premultiplied )
571 : : {
572 : 0 : pImage = new QImage( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
573 : 0 : }
574 : 0 : else if ( alphaOnly && originalFormat != QImage::Format_ARGB32 )
575 : : {
576 : 0 : pImage = new QImage( image.convertToFormat( QImage::Format_ARGB32 ) );
577 : 0 : }
578 : :
579 : 0 : if ( alphaOnly )
580 : 0 : i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
581 : :
582 : 0 : StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn, true, i1, i2 );
583 : 0 : runLineOperation( *pImage, topToBottomBlur );
584 : :
585 : 0 : StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow, true, i1, i2 );
586 : 0 : runLineOperation( *pImage, leftToRightBlur );
587 : :
588 : 0 : StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn, false, i1, i2 );
589 : 0 : runLineOperation( *pImage, bottomToTopBlur );
590 : :
591 : 0 : StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow, false, i1, i2 );
592 : 0 : runLineOperation( *pImage, rightToLeftBlur );
593 : :
594 : 0 : if ( pImage->format() != originalFormat )
595 : : {
596 : 0 : image = pImage->convertToFormat( originalFormat );
597 : 0 : delete pImage;
598 : 0 : }
599 : 0 : }
600 : :
601 : : //gaussian blur
602 : :
603 : 0 : QImage *QgsImageOperation::gaussianBlur( QImage &image, const int radius )
604 : : {
605 : 0 : int width = image.width();
606 : 0 : int height = image.height();
607 : :
608 : 0 : if ( radius <= 0 )
609 : : {
610 : : //just make an unchanged copy
611 : 0 : QImage *copy = new QImage( image.copy() );
612 : 0 : return copy;
613 : : }
614 : :
615 : 0 : double *kernel = createGaussianKernel( radius );
616 : :
617 : : //ensure correct source format.
618 : 0 : QImage::Format originalFormat = image.format();
619 : 0 : QImage *pImage = ℑ
620 : 0 : if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
621 : : {
622 : 0 : pImage = new QImage( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
623 : 0 : }
624 : :
625 : : //blur along rows
626 : 0 : QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
627 : 0 : GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel );
628 : 0 : runRectOperation( *pImage, rowBlur );
629 : :
630 : : //blur along columns
631 : 0 : QImage *yBlurImage = new QImage( width, height, QImage::Format_ARGB32_Premultiplied );
632 : 0 : GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage, kernel );
633 : 0 : runRectOperation( xBlurImage, colBlur );
634 : :
635 : 0 : delete[] kernel;
636 : :
637 : 0 : if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
638 : : {
639 : 0 : QImage *convertedImage = new QImage( yBlurImage->convertToFormat( originalFormat ) );
640 : 0 : delete yBlurImage;
641 : 0 : delete pImage;
642 : 0 : return convertedImage;
643 : : }
644 : :
645 : 0 : return yBlurImage;
646 : 0 : }
647 : :
648 : 0 : void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
649 : : {
650 : 0 : int width = block.image->width();
651 : 0 : int height = block.image->height();
652 : 0 : int sourceBpl = block.image->bytesPerLine();
653 : :
654 : 0 : unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
655 : 0 : QRgb *destRef = nullptr;
656 : 0 : if ( mDirection == ByRow )
657 : : {
658 : 0 : unsigned char *sourceFirstLine = block.image->scanLine( 0 );
659 : : unsigned char *sourceRef;
660 : :
661 : : //blur along rows
662 : 0 : for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
663 : : {
664 : 0 : sourceRef = sourceFirstLine;
665 : 0 : destRef = reinterpret_cast< QRgb * >( outputLineRef );
666 : 0 : for ( int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
667 : : {
668 : 0 : *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
669 : 0 : }
670 : 0 : }
671 : 0 : }
672 : : else
673 : : {
674 : 0 : unsigned char *sourceRef = block.image->scanLine( block.beginLine );
675 : 0 : for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
676 : : {
677 : 0 : destRef = reinterpret_cast< QRgb * >( outputLineRef );
678 : 0 : for ( int x = 0; x < width; ++x, ++destRef )
679 : : {
680 : 0 : *destRef = gaussianBlurHorizontal( x, sourceRef, width );
681 : 0 : }
682 : 0 : }
683 : : }
684 : 0 : }
685 : :
686 : 0 : inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical( const int posy, unsigned char *sourceFirstLine, const int sourceBpl, const int height )
687 : : {
688 : 0 : double r = 0;
689 : 0 : double b = 0;
690 : 0 : double g = 0;
691 : 0 : double a = 0;
692 : : int y;
693 : : unsigned char *ref;
694 : :
695 : 0 : for ( int i = 0; i <= mRadius * 2; ++i )
696 : : {
697 : 0 : y = std::clamp( posy + ( i - mRadius ), 0, height - 1 );
698 : 0 : ref = sourceFirstLine + sourceBpl * y;
699 : :
700 : 0 : QRgb *refRgb = reinterpret_cast< QRgb * >( ref );
701 : 0 : r += mKernel[i] * qRed( *refRgb );
702 : 0 : g += mKernel[i] * qGreen( *refRgb );
703 : 0 : b += mKernel[i] * qBlue( *refRgb );
704 : 0 : a += mKernel[i] * qAlpha( *refRgb );
705 : 0 : }
706 : :
707 : 0 : return qRgba( r, g, b, a );
708 : : }
709 : :
710 : 0 : inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal( const int posx, unsigned char *sourceFirstLine, const int width )
711 : : {
712 : 0 : double r = 0;
713 : 0 : double b = 0;
714 : 0 : double g = 0;
715 : 0 : double a = 0;
716 : : int x;
717 : : unsigned char *ref;
718 : :
719 : 0 : for ( int i = 0; i <= mRadius * 2; ++i )
720 : : {
721 : 0 : x = std::clamp( posx + ( i - mRadius ), 0, width - 1 );
722 : 0 : ref = sourceFirstLine + x * 4;
723 : :
724 : 0 : QRgb *refRgb = reinterpret_cast< QRgb * >( ref );
725 : 0 : r += mKernel[i] * qRed( *refRgb );
726 : 0 : g += mKernel[i] * qGreen( *refRgb );
727 : 0 : b += mKernel[i] * qBlue( *refRgb );
728 : 0 : a += mKernel[i] * qAlpha( *refRgb );
729 : 0 : }
730 : :
731 : 0 : return qRgba( r, g, b, a );
732 : : }
733 : :
734 : :
735 : 0 : double *QgsImageOperation::createGaussianKernel( const int radius )
736 : : {
737 : 0 : double *kernel = new double[ radius * 2 + 1 ];
738 : 0 : double sigma = radius / 3.0;
739 : 0 : double twoSigmaSquared = 2 * sigma * sigma;
740 : 0 : double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
741 : 0 : double expCoefficient = -1.0 / twoSigmaSquared;
742 : :
743 : 0 : double sum = 0;
744 : : double result;
745 : 0 : for ( int i = 0; i <= radius; ++i )
746 : : {
747 : 0 : result = coefficient * std::exp( i * i * expCoefficient );
748 : 0 : kernel[ radius - i ] = result;
749 : 0 : sum += result;
750 : 0 : if ( i > 0 )
751 : : {
752 : 0 : kernel[radius + i] = result;
753 : 0 : sum += result;
754 : 0 : }
755 : 0 : }
756 : : //normalize
757 : 0 : for ( int i = 0; i <= radius * 2; ++i )
758 : : {
759 : 0 : kernel[i] /= sum;
760 : 0 : }
761 : 0 : return kernel;
762 : : }
763 : :
764 : :
765 : : // flip
766 : :
767 : 0 : void QgsImageOperation::flipImage( QImage &image, QgsImageOperation::FlipType type )
768 : : {
769 : 0 : FlipLineOperation flipOperation( type == QgsImageOperation::FlipHorizontal ? QgsImageOperation::ByRow : QgsImageOperation::ByColumn );
770 : 0 : runLineOperation( image, flipOperation );
771 : 0 : }
772 : :
773 : 0 : QRect QgsImageOperation::nonTransparentImageRect( const QImage &image, QSize minSize, bool center )
774 : : {
775 : 0 : int width = image.width();
776 : 0 : int height = image.height();
777 : 0 : int xmin = width;
778 : 0 : int xmax = 0;
779 : 0 : int ymin = height;
780 : 0 : int ymax = 0;
781 : :
782 : : // scan down till we hit something
783 : 0 : for ( int y = 0; y < height; ++y )
784 : : {
785 : 0 : bool found = false;
786 : 0 : const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
787 : 0 : for ( int x = 0; x < width; ++x )
788 : : {
789 : 0 : if ( qAlpha( imgScanline[x] ) )
790 : : {
791 : 0 : ymin = y;
792 : 0 : ymax = y;
793 : 0 : xmin = x;
794 : 0 : xmax = x;
795 : 0 : found = true;
796 : 0 : break;
797 : : }
798 : 0 : }
799 : 0 : if ( found )
800 : 0 : break;
801 : 0 : }
802 : :
803 : : //scan up till we hit something
804 : 0 : for ( int y = height - 1; y >= ymin; --y )
805 : : {
806 : 0 : bool found = false;
807 : 0 : const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
808 : 0 : for ( int x = 0; x < width; ++x )
809 : : {
810 : 0 : if ( qAlpha( imgScanline[x] ) )
811 : : {
812 : 0 : ymax = y;
813 : 0 : xmin = std::min( xmin, x );
814 : 0 : xmax = std::max( xmax, x );
815 : 0 : found = true;
816 : 0 : break;
817 : : }
818 : 0 : }
819 : 0 : if ( found )
820 : 0 : break;
821 : 0 : }
822 : :
823 : : //scan left to right till we hit something, using a refined y region
824 : 0 : for ( int y = ymin; y <= ymax; ++y )
825 : : {
826 : 0 : const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
827 : 0 : for ( int x = 0; x < xmin; ++x )
828 : : {
829 : 0 : if ( qAlpha( imgScanline[x] ) )
830 : : {
831 : 0 : xmin = x;
832 : 0 : break;
833 : : }
834 : 0 : }
835 : 0 : }
836 : :
837 : : //scan right to left till we hit something, using the refined y region
838 : 0 : for ( int y = ymin; y <= ymax; ++y )
839 : : {
840 : 0 : const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
841 : 0 : for ( int x = width - 1; x > xmax; --x )
842 : : {
843 : 0 : if ( qAlpha( imgScanline[x] ) )
844 : : {
845 : 0 : xmax = x;
846 : 0 : break;
847 : : }
848 : 0 : }
849 : 0 : }
850 : :
851 : 0 : if ( minSize.isValid() )
852 : : {
853 : 0 : if ( xmax - xmin < minSize.width() ) // centers image on x
854 : : {
855 : 0 : xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
856 : 0 : xmax = xmin + minSize.width();
857 : 0 : }
858 : 0 : if ( ymax - ymin < minSize.height() ) // centers image on y
859 : : {
860 : 0 : ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
861 : 0 : ymax = ymin + minSize.height();
862 : 0 : }
863 : 0 : }
864 : 0 : if ( center )
865 : : {
866 : : // recompute min and max to center image
867 : 0 : const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
868 : 0 : const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
869 : 0 : xmin = std::max( 0, width / 2 - dx );
870 : 0 : xmax = std::min( width, width / 2 + dx );
871 : 0 : ymin = std::max( 0, height / 2 - dy );
872 : 0 : ymax = std::min( height, height / 2 + dy );
873 : 0 : }
874 : :
875 : 0 : return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
876 : : }
877 : :
878 : 0 : QImage QgsImageOperation::cropTransparent( const QImage &image, QSize minSize, bool center )
879 : : {
880 : 0 : return image.copy( QgsImageOperation::nonTransparentImageRect( image, minSize, center ) );
881 : : }
882 : :
883 : 0 : void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef, const int lineLength, const int bytesPerLine )
884 : : {
885 : 0 : int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
886 : :
887 : : //store temporary line
888 : 0 : unsigned char *p = reinterpret_cast< unsigned char * >( startRef );
889 : 0 : unsigned char *tempLine = new unsigned char[ lineLength * 4 ];
890 : 0 : for ( int i = 0; i < lineLength * 4; ++i, p += increment )
891 : : {
892 : 0 : tempLine[i++] = *( p++ );
893 : 0 : tempLine[i++] = *( p++ );
894 : 0 : tempLine[i++] = *( p++ );
895 : 0 : tempLine[i] = *( p );
896 : 0 : p -= 3;
897 : 0 : }
898 : :
899 : : //write values back in reverse order
900 : 0 : p = reinterpret_cast< unsigned char * >( startRef );
901 : 0 : for ( int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
902 : : {
903 : 0 : *( p++ ) = tempLine[i++];
904 : 0 : *( p++ ) = tempLine[i++];
905 : 0 : *( p++ ) = tempLine[i++];
906 : 0 : *( p ) = tempLine[i];
907 : 0 : p -= 3;
908 : 0 : }
909 : :
910 : 0 : delete[] tempLine;
911 : 0 : }
912 : :
913 : :
914 : :
915 : :
|