1 /***************************************************************************
2                            qgsimageoperation.h
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 #ifndef QGSIMAGEOPERATION_H
19 #define QGSIMAGEOPERATION_H
20 
21 #include <QImage>
22 #include "qgis_sip.h"
23 #include <QColor>
24 
25 #include "qgis_core.h"
26 #include <cmath>
27 
28 class QgsColorRamp;
29 
30 /**
31  * \ingroup core
32  * \class QgsImageOperation
33  * \brief Contains operations and filters which apply to QImages
34  *
35  * A set of optimised pixel manipulation operations and filters which can be applied
36  * to QImages. All operations only apply to ARGB32 format images, and it is left up
37  * to the calling procedure to ensure that any passed images are of the correct
38  * format.
39  *
40  * Operations are written to either modify an image in place or return a new image, depending
41  * on which is faster for the particular operation.
42  *
43  * \since QGIS 2.7
44  */
45 class CORE_EXPORT QgsImageOperation
46 {
47 
48   public:
49 
50     /**
51      * Modes for converting a QImage to grayscale
52      */
53     enum GrayscaleMode
54     {
55       GrayscaleLightness, //!< Keep the lightness of the color, drops the saturation
56       GrayscaleLuminosity, //!< Grayscale by perceptual luminosity (weighted sum of color RGB components)
57       GrayscaleAverage, //!< Grayscale by taking average of color RGB components
58       GrayscaleOff //!< No change
59     };
60 
61     /**
62      * Flip operation types
63      */
64     enum FlipType
65     {
66       FlipHorizontal, //!< Flip the image horizontally
67       FlipVertical //!< Flip the image vertically
68     };
69 
70     /**
71      * Convert a QImage to a grayscale image. Alpha channel is preserved.
72      * \param image QImage to convert
73      * \param mode mode to use during grayscale conversion
74      */
75     static void convertToGrayscale( QImage &image, GrayscaleMode mode = GrayscaleLuminosity );
76 
77     /**
78      * Alter the brightness or contrast of a QImage.
79      * \param image QImage to alter
80      * \param brightness brightness value, in the range -255 to 255. A brightness value of 0 indicates
81      * no change to brightness, a negative value will darken the image, and a positive value will brighten
82      * the image.
83      * \param contrast contrast value. Must be a positive or zero value. A value of 1.0 indicates no change
84      * to the contrast, a value of 0 represents an image with 0 contrast, and a value > 1.0 will increase the
85      * contrast of the image.
86      */
87     static void adjustBrightnessContrast( QImage &image, int brightness, double contrast );
88 
89     /**
90      * Alter the hue or saturation of a QImage.
91      * \param image QImage to alter
92      * \param saturation double between 0 and 2 inclusive, where 0 = desaturate and 1.0 = no change
93      * \param colorizeColor color to use for colorizing image. Set to an invalid QColor to disable
94      * colorization.
95      * \param colorizeStrength double between 0 and 1, where 0 = no colorization and 1.0 = full colorization
96      */
97     static void adjustHueSaturation( QImage &image, double saturation, const QColor &colorizeColor = QColor(),
98                                      double colorizeStrength = 1.0 );
99 
100     /**
101      * Multiplies opacity of image pixel values by a factor.
102      * \param image QImage to alter
103      * \param factor factor to multiple pixel's opacity by
104      */
105     static void multiplyOpacity( QImage &image, double factor );
106 
107     /**
108      * Overlays a color onto an image. This operation retains the alpha channel of the
109      * original image, but replaces all image pixel colors with the specified color.
110      * \param image QImage to alter
111      * \param color color to overlay (any alpha component of the color is ignored)
112      */
113     static void overlayColor( QImage &image, const QColor &color );
114 
115     //! Struct for storing properties of a distance transform operation
116     struct DistanceTransformProperties
117     {
118 
119       /**
120        * Set to TRUE to perform the distance transform on transparent pixels
121        * in the source image, set to FALSE to perform the distance transform
122        * on opaque pixels
123        */
124       bool shadeExterior = true;
125 
126       /**
127        * Set to TRUE to automatically calculate the maximum distance in the
128        * transform to use as the spread value
129        */
130       bool useMaxDistance = true;
131 
132       /**
133        * Maximum distance (in pixels) for the distance transform shading to
134        * spread
135        */
136       double spread = 10.0;
137 
138       /**
139        * Color ramp to use for shading the distance transform
140        */
141       QgsColorRamp *ramp = nullptr;
142     };
143 
144     /**
145      * Performs a distance transform on the source image and shades the result
146      * using a color ramp.
147      * \param image QImage to alter
148      * \param properties DistanceTransformProperties object with parameters
149      * for the distance transform operation
150      */
151     static void distanceTransform( QImage &image, const QgsImageOperation::DistanceTransformProperties &properties );
152 
153     /**
154      * Performs a stack blur on an image. Stack blur represents a good balance between
155      * speed and blur quality.
156      * \param image QImage to blur
157      * \param radius blur radius in pixels, maximum value of 16
158      * \param alphaOnly set to TRUE to blur only the alpha component of the image
159      * \note for fastest operation, ensure the source image is ARGB32_Premultiplied if
160      * alphaOnly is set to FALSE, or ARGB32 if alphaOnly is TRUE
161      */
162     static void stackBlur( QImage &image, int radius, bool alphaOnly = false );
163 
164     /**
165      * Performs a gaussian blur on an image. Gaussian blur is slower but results in a high
166      * quality blur.
167      * \param image QImage to blur
168      * \param radius blur radius in pixels
169      * \returns blurred image
170      * \note for fastest operation, ensure the source image is ARGB32_Premultiplied
171      */
172     static QImage *gaussianBlur( QImage &image, int radius ) SIP_FACTORY;
173 
174     /**
175      * Flips an image horizontally or vertically
176      * \param image QImage to flip
177      * \param type type of flip to perform (horizontal or vertical)
178      */
179     static void flipImage( QImage &image, FlipType type );
180 
181     /**
182      * Calculates the non-transparent region of an image.
183      * \param image source image
184      * \param minSize minimum size for returned region, if desired. If the
185      * non-transparent region of the image is smaller than this minimum size,
186      * it will be centered in the returned rectangle.
187      * \param center return rectangle will be centered on the center of the original image if set to TRUE
188      * \see cropTransparent
189      * \since QGIS 2.9
190      */
191     static QRect nonTransparentImageRect( const QImage &image, QSize minSize = QSize(), bool center = false );
192 
193     /**
194      * Crop any transparent border from around an image.
195      * \param image source image
196      * \param minSize minimum size for cropped image, if desired. If the
197      * cropped image is smaller than the minimum size, it will be centered
198      * in the returned image.
199      * \param center cropped image will be centered on the center of the original image if set to TRUE
200      * \since QGIS 2.9
201      */
202     static QImage cropTransparent( const QImage &image, QSize minSize = QSize(), bool center = false );
203 
204   private:
205 
206     //for blocked operations
207     enum LineOperationDirection
208     {
209       ByRow,
210       ByColumn
211     };
212     template <class BlockOperation> static void runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction );
213     struct ImageBlock
214     {
215       unsigned int beginLine;
216       unsigned int endLine;
217       unsigned int lineLength;
218       QImage *image = nullptr;
219     };
220 
221     //for rect operations
222     template <typename RectOperation> static void runRectOperation( QImage &image, RectOperation &operation );
223     template <class RectOperation> static void runRectOperationOnWholeImage( QImage &image, RectOperation &operation );
224 
225     //for per pixel operations
226     template <class PixelOperation> static void runPixelOperation( QImage &image, PixelOperation &operation );
227     template <class PixelOperation> static void runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation );
228     template <class PixelOperation>
229     struct ProcessBlockUsingPixelOperation
230     {
ProcessBlockUsingPixelOperationProcessBlockUsingPixelOperation231       explicit ProcessBlockUsingPixelOperation( PixelOperation &operation )
232         : mOperation( operation ) { }
233 
234       typedef void result_type;
235 
operatorProcessBlockUsingPixelOperation236       void operator()( ImageBlock &block )
237       {
238         for ( unsigned int y = block.beginLine; y < block.endLine; ++y )
239         {
240           QRgb *ref = reinterpret_cast< QRgb * >( block.image->scanLine( y ) );
241           for ( unsigned int x = 0; x < block.lineLength; ++x )
242           {
243             mOperation( ref[x], x, y );
244           }
245         }
246       }
247 
248       PixelOperation &mOperation;
249     };
250 
251     //for linear operations
252     template <typename LineOperation> static void runLineOperation( QImage &image, LineOperation &operation );
253     template <class LineOperation> static void runLineOperationOnWholeImage( QImage &image, LineOperation &operation );
254     template <class LineOperation>
255     struct ProcessBlockUsingLineOperation
256     {
ProcessBlockUsingLineOperationProcessBlockUsingLineOperation257       explicit ProcessBlockUsingLineOperation( LineOperation &operation )
258         : mOperation( operation ) { }
259 
260       typedef void result_type;
261 
operatorProcessBlockUsingLineOperation262       void operator()( ImageBlock &block )
263       {
264         //do something with whole lines
265         int bpl = block.image->bytesPerLine();
266         if ( mOperation.direction() == ByRow )
267         {
268           for ( unsigned int y = block.beginLine; y < block.endLine; ++y )
269           {
270             QRgb *ref = reinterpret_cast< QRgb * >( block.image->scanLine( y ) );
271             mOperation( ref, block.lineLength, bpl );
272           }
273         }
274         else
275         {
276           //by column
277           unsigned char *ref = block.image->scanLine( 0 ) + 4 * block.beginLine;
278           for ( unsigned int x = block.beginLine; x < block.endLine; ++x, ref += 4 )
279           {
280             mOperation( reinterpret_cast< QRgb * >( ref ), block.lineLength, bpl );
281           }
282         }
283       }
284 
285       LineOperation &mOperation;
286     };
287 
288 
289     //individual operation implementations
290 
291     class GrayscalePixelOperation
292     {
293       public:
GrayscalePixelOperation(const GrayscaleMode mode)294         explicit GrayscalePixelOperation( const GrayscaleMode mode )
295           : mMode( mode )
296         {  }
297 
298         void operator()( QRgb &rgb, int x, int y );
299 
300       private:
301         GrayscaleMode mMode;
302     };
303     static void grayscaleLightnessOp( QRgb &rgb );
304     static void grayscaleLuminosityOp( QRgb &rgb );
305     static void grayscaleAverageOp( QRgb &rgb );
306 
307 
308     class BrightnessContrastPixelOperation
309     {
310       public:
BrightnessContrastPixelOperation(const int brightness,const double contrast)311         BrightnessContrastPixelOperation( const int brightness, const double contrast )
312           : mBrightness( brightness )
313           , mContrast( contrast )
314         {  }
315 
316         void operator()( QRgb &rgb, int x, int y );
317 
318       private:
319         int mBrightness;
320         double mContrast;
321     };
322 
323 
324     class HueSaturationPixelOperation
325     {
326       public:
HueSaturationPixelOperation(const double saturation,const bool colorize,const int colorizeHue,const int colorizeSaturation,const double colorizeStrength)327         HueSaturationPixelOperation( const double saturation, const bool colorize,
328                                      const int colorizeHue, const int colorizeSaturation,
329                                      const double colorizeStrength )
330           : mSaturation( saturation )
331           , mColorize( colorize )
332           , mColorizeHue( colorizeHue )
333           , mColorizeSaturation( colorizeSaturation )
334           , mColorizeStrength( colorizeStrength )
335         {  }
336 
337         void operator()( QRgb &rgb, int x, int y );
338 
339       private:
340         double mSaturation; // [0, 2], 1 = no change
341         bool mColorize;
342         int mColorizeHue;
343         int mColorizeSaturation;
344         double mColorizeStrength; // [0,1]
345     };
346     static int adjustColorComponent( int colorComponent, int brightness, double contrastFactor );
347 
348 
349     class MultiplyOpacityPixelOperation
350     {
351       public:
MultiplyOpacityPixelOperation(const double factor)352         explicit MultiplyOpacityPixelOperation( const double factor )
353           : mFactor( factor )
354         { }
355 
356         void operator()( QRgb &rgb, int x, int y );
357 
358       private:
359         double mFactor;
360     };
361 
362     class ConvertToArrayPixelOperation
363     {
364       public:
365         ConvertToArrayPixelOperation( const int width, double *array, const bool exterior = true )
mWidth(width)366           : mWidth( width )
367           , mArray( array )
368           , mExterior( exterior )
369         {
370         }
371 
372         void operator()( QRgb &rgb, int x, int y );
373 
374       private:
375         int mWidth;
376         double *mArray = nullptr;
377         bool mExterior;
378     };
379 
380     class ShadeFromArrayOperation
381     {
382       public:
ShadeFromArrayOperation(const int width,double * array,const double spread,const DistanceTransformProperties & properties)383         ShadeFromArrayOperation( const int width, double *array, const double spread,
384                                  const DistanceTransformProperties &properties )
385           : mWidth( width )
386           , mArray( array )
387           , mSpread( spread )
388           , mProperties( properties )
389         {
390           mSpreadSquared = std::pow( mSpread, 2.0 );
391         }
392 
393         void operator()( QRgb &rgb, int x, int y );
394 
395       private:
396         int mWidth;
397         double *mArray = nullptr;
398         double mSpread;
399         double mSpreadSquared;
400         const DistanceTransformProperties &mProperties;
401     };
402     static void distanceTransform2d( double *im, int width, int height );
403     static void distanceTransform1d( double *f, int n, int *v, double *z, double *d );
404     static double maxValueInDistanceTransformArray( const double *array, unsigned int size );
405 
406 
407     class StackBlurLineOperation
408     {
409       public:
StackBlurLineOperation(int alpha,LineOperationDirection direction,bool forwardDirection,int i1,int i2)410         StackBlurLineOperation( int alpha, LineOperationDirection direction, bool forwardDirection, int i1, int i2 )
411           : mAlpha( alpha )
412           , mDirection( direction )
413           , mForwardDirection( forwardDirection )
414           , mi1( i1 )
415           , mi2( i2 )
416         { }
417 
418         typedef void result_type;
419 
direction()420         LineOperationDirection direction() { return mDirection; }
421 
operator()422         void operator()( QRgb *startRef, int lineLength, int bytesPerLine )
423         {
424           unsigned char *p = reinterpret_cast< unsigned char * >( startRef );
425           int rgba[4];
426           int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
427           if ( !mForwardDirection )
428           {
429             p += ( lineLength - 1 ) * increment;
430             increment = -increment;
431           }
432 
433           for ( int i = mi1; i <= mi2; ++i )
434           {
435             rgba[i] = p[i] << 4;
436           }
437 
438           p += increment;
439           for ( int j = 1; j < lineLength; ++j, p += increment )
440           {
441             for ( int i = mi1; i <= mi2; ++i )
442             {
443               p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * mAlpha / 16 ) >> 4;
444             }
445           }
446         }
447 
448       private:
449         int mAlpha;
450         LineOperationDirection mDirection;
451         bool mForwardDirection;
452         int mi1;
453         int mi2;
454     };
455 
456     static double *createGaussianKernel( int radius );
457 
458     class GaussianBlurOperation
459     {
460       public:
GaussianBlurOperation(int radius,LineOperationDirection direction,QImage * destImage,double * kernel)461         GaussianBlurOperation( int radius, LineOperationDirection direction, QImage *destImage, double *kernel )
462           : mRadius( radius )
463           , mDirection( direction )
464           , mDestImage( destImage )
465           , mDestImageBpl( destImage->bytesPerLine() )
466           , mKernel( kernel )
467         {}
468 
469         typedef void result_type;
470 
471         void operator()( ImageBlock &block );
472 
473       private:
474         int mRadius;
475         LineOperationDirection mDirection;
476         QImage *mDestImage = nullptr;
477         int mDestImageBpl;
478         double *mKernel = nullptr;
479 
480         inline QRgb gaussianBlurVertical( int posy, unsigned char *sourceFirstLine, int sourceBpl, int height );
481         inline QRgb gaussianBlurHorizontal( int posx, unsigned char *sourceFirstLine, int width );
482     };
483 
484     //flip
485 
486 
487     class FlipLineOperation
488     {
489       public:
FlipLineOperation(LineOperationDirection direction)490         explicit FlipLineOperation( LineOperationDirection direction )
491           : mDirection( direction )
492         { }
493 
494         typedef void result_type;
495 
direction()496         LineOperationDirection direction() { return mDirection; }
497 
498         void operator()( QRgb *startRef, int lineLength, int bytesPerLine );
499 
500       private:
501         LineOperationDirection mDirection;
502     };
503 
504 
505 };
506 
507 #endif // QGSIMAGEOPERATION_H
508 
509