Skip to content

Commit be2d6d1

Browse files
committedJan 21, 2015
New QgsImageOperation class for operations that modify QImages
Contains framework for multithreaded operations on QImages, and numerous operations such as grayscale, hue/saturation, brightness/ contrast modification, flip, blur, distance transform, alpha modification and color overlays.
1 parent 3f88ee5 commit be2d6d1

File tree

34 files changed

+1497
-0
lines changed

34 files changed

+1497
-0
lines changed
 

‎python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ INCLUDE_DIRECTORIES(
8181
../src/core/pal
8282
../src/core/composer
8383
../src/core/diagram
84+
../src/core/effects
8485
../src/core/dxf
8586
../src/core/gps
8687
../src/core/layertree

‎python/core/core.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@
170170
%Include diagram/qgspiediagram.sip
171171
%Include diagram/qgstextdiagram.sip
172172

173+
%Include effects/qgsimageoperation.sip
174+
173175
%Include gps/qgsgpsconnection.sip
174176
%Include gps/qgsgpsconnectionregistry.sip
175177
%Include gps/qgsgpsdconnection.sip
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/** \ingroup core
2+
* \class QgsImageOperation
3+
* \brief Contains operations and filters which apply to QImages
4+
*
5+
* A set of optimised pixel manipulation operations and filters which can be applied
6+
* to QImages.
7+
* \note Added in version 2.7
8+
*/
9+
class QgsImageOperation
10+
{
11+
%TypeHeaderCode
12+
#include <qgsimageoperation.h>
13+
%End
14+
15+
public:
16+
17+
/** Modes for converting a QImage to grayscale
18+
*/
19+
enum GrayscaleMode
20+
{
21+
GrayscaleLightness, /*< keep the lightness of the color, drops the saturation */
22+
GrayscaleLuminosity, /*< grayscale by perceptual luminosity (weighted sum of color RGB components) */
23+
GrayscaleAverage /*< grayscale by taking average of color RGB components */
24+
};
25+
26+
/** Flip operation types
27+
*/
28+
enum FlipType
29+
{
30+
FlipHorizontal, /*< flip the image horizontally */
31+
FlipVertical /*< flip the image vertically */
32+
};
33+
34+
/**Convert a QImage to a grayscale image. Alpha channel is preserved.
35+
* @param image QImage to convert
36+
* @param mode mode to use during grayscale conversion
37+
*/
38+
static void convertToGrayscale( QImage &image, const GrayscaleMode mode = GrayscaleLuminosity );
39+
40+
/**Alter the brightness or contrast of a QImage.
41+
* @param image QImage to alter
42+
* @param brightness brightness value, in the range -255 to 255. A brightness value of 0 indicates
43+
* no change to brightness, a negative value will darken the image, and a positive value will brighten
44+
* the image.
45+
* @param contrast contrast value. Must be a positive or zero value. A value of 1.0 indicates no change
46+
* to the contrast, a value of 0 represents an image with 0 contrast, and a value > 1.0 will increase the
47+
* contrast of the image.
48+
*/
49+
static void adjustBrightnessContrast( QImage &image, const int brightness, const double contrast );
50+
51+
/**Alter the hue or saturation of a QImage.
52+
* @param image QImage to alter
53+
* @param saturation double between 0 and 2 inclusive, where 0 = desaturate and 1.0 = no change
54+
* @param colorizeColor color to use for colorizing image. Set to an invalid QColor to disable
55+
* colorization.
56+
* @param colorizeStrength double between 0 and 1, where 0 = no colorization and 1.0 = full colorization
57+
*/
58+
static void adjustHueSaturation( QImage &image, const double saturation, const QColor& colorizeColor = QColor(),
59+
const double colorizeStrength = 1.0 );
60+
61+
/**Multiplies opacity of image pixel values by a factor.
62+
* @param image QImage to alter
63+
* @param factor factor to multiple pixel's opacity by
64+
*/
65+
static void multiplyOpacity( QImage &image, const double factor );
66+
67+
/**Overlays a color onto an image. This operation retains the alpha channel of the
68+
* original image, but replaces all image pixel colors with the specified color.
69+
* @param image QImage to alter
70+
* @param color color to overlay (any alpha component of the color is ignored)
71+
*/
72+
static void overlayColor( QImage &image, const QColor& color );
73+
74+
/**Struct for storing properties of a distance transform operation*/
75+
struct DistanceTransformProperties
76+
{
77+
/**Set to true to perform the distance transform on transparent pixels
78+
* in the source image, set to false to perform the distance transform
79+
* on opaque pixels
80+
*/
81+
bool shadeExterior;
82+
/**Set to true to automatically calculate the maximum distance in the
83+
* transform to use as the spread value
84+
*/
85+
bool useMaxDistance;
86+
/**Maximum distance (in pixels) for the distance transform shading to
87+
* spread
88+
*/
89+
double spread;
90+
/**Color ramp to use for shading the distance transform
91+
*/
92+
QgsVectorColorRampV2* ramp;
93+
};
94+
95+
/**Performs a distance transform on the source image and shades the result
96+
* using a color ramp.
97+
* @param image QImage to alter
98+
* @param properties DistanceTransformProperties object with parameters
99+
* for the distance transform operation
100+
*/
101+
static void distanceTransform( QImage &image, const QgsImageOperation::DistanceTransformProperties& properties );
102+
103+
/**Performs a stack blur on an image. Stack blur represents a good balance between
104+
* speed and blur quality.
105+
* @param image QImage to blur
106+
* @param radius blur radius in pixels, maximum value of 16
107+
* @param alphaOnly set to true to blur only the alpha component of the image
108+
*/
109+
static void stackBlur( QImage &image, const int radius, const bool alphaOnly = false );
110+
111+
/**Flips an image horizontally or vertically
112+
* @param image QImage to flip
113+
* @param type type of flip to perform (horizontal or vertical)
114+
*/
115+
static void flipImage( QImage &image, FlipType type );
116+
117+
};

‎src/core/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ SET(QGIS_CORE_SRCS
4646
diagram/qgspiediagram.cpp
4747
diagram/qgstextdiagram.cpp
4848
diagram/qgshistogramdiagram.cpp
49+
50+
effects/qgsimageoperation.cpp
4951

5052
layertree/qgslayertreegroup.cpp
5153
layertree/qgslayertreelayer.cpp
@@ -554,6 +556,8 @@ SET(QGIS_CORE_HDRS
554556
diagram/qgspiediagram.h
555557
diagram/qgstextdiagram.h
556558
diagram/qgshistogramdiagram.h
559+
560+
effects/qgsimageoperation.h
557561

558562
composer/qgsaddremovemultiframecommand.h
559563
composer/qgscomposerarrow.h
@@ -647,6 +651,7 @@ INCLUDE_DIRECTORIES(
647651
${CMAKE_CURRENT_SOURCE_DIR}
648652
composer
649653
dxf
654+
effects
650655
layertree
651656
pal
652657
raster

‎src/core/effects/qgsimageoperation.cpp

Lines changed: 585 additions & 0 deletions
Large diffs are not rendered by default.

‎src/core/effects/qgsimageoperation.h

Lines changed: 402 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
99
${CMAKE_CURRENT_BINARY_DIR}
1010
${CMAKE_SOURCE_DIR}/src/core
1111
${CMAKE_SOURCE_DIR}/src/core/composer
12+
${CMAKE_SOURCE_DIR}/src/core/effects
1213
${CMAKE_SOURCE_DIR}/src/core/layertree
1314
${CMAKE_SOURCE_DIR}/src/core/raster
1415
${CMAKE_SOURCE_DIR}/src/core/symbology-ng
@@ -145,3 +146,4 @@ ADD_QGIS_TEST(vectorlayerjoinbuffer testqgsvectorlayerjoinbuffer.cpp )
145146
ADD_QGIS_TEST(maplayerstylemanager testqgsmaplayerstylemanager.cpp )
146147
ADD_QGIS_TEST(pointlocatortest testqgspointlocator.cpp )
147148
ADD_QGIS_TEST(snappingutilstest testqgssnappingutils.cpp )
149+
ADD_QGIS_TEST(imageoperationtest testqgsimageoperation.cpp)
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
/***************************************************************************
2+
testqgsimageoperations.cpp
3+
--------------------------
4+
begin : January 2015
5+
copyright : (C) 2015 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot 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 "qgsvectorcolorrampv2.h"
20+
#include <QObject>
21+
#include <QtTest/QtTest>
22+
#include "qgsrenderchecker.h"
23+
24+
class TestQgsImageOperation : public QObject
25+
{
26+
Q_OBJECT
27+
28+
private slots:
29+
void initTestCase();// will be called before the first testfunction is executed.
30+
void cleanupTestCase();// will be called after the last testfunction was executed.
31+
void init();// will be called before each testfunction is executed.
32+
void cleanup();// will be called after every testfunction.
33+
void smallImageOp(); //test operation on small image (single threaded op)
34+
35+
//grayscale
36+
void grayscaleLightness();
37+
void grayscaleLuminosity();
38+
void grayscaleAverage();
39+
40+
//brightness/contrast
41+
void brightnessContrastNoChange();
42+
void increaseBrightness();
43+
void decreaseBrightness();
44+
void increaseContrast();
45+
void decreaseContrast();
46+
47+
//hue/saturation
48+
void hueSaturationNoChange();
49+
void increaseSaturation();
50+
void decreaseSaturation();
51+
void colorizeFull();
52+
void colorizePartial();
53+
54+
//multiply opacity
55+
void opacityNoChange();
56+
void opacityIncrease();
57+
void opacityDecrease();
58+
59+
//overlay color
60+
void overlayColor();
61+
62+
//distance transform
63+
void distanceTransformMaxDist();
64+
void distanceTransformSetSpread();
65+
void distanceTransformInterior();
66+
67+
//stack blur
68+
void stackBlur();
69+
void alphaOnlyBlur();
70+
71+
//flip
72+
void flipHorizontal();
73+
void flipVertical();
74+
75+
private:
76+
77+
QString mReport;
78+
QString mSampleImage;
79+
80+
bool imageCheck( QString testName , QImage &image, int mismatchCount );
81+
82+
};
83+
84+
void TestQgsImageOperation::initTestCase()
85+
{
86+
mReport += "<h1>Image Operation Tests</h1>\n";
87+
mSampleImage = QString( TEST_DATA_DIR ) + QDir::separator() + "sample_image.png";
88+
}
89+
90+
void TestQgsImageOperation::cleanupTestCase()
91+
{
92+
QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html";
93+
QFile myFile( myReportFile );
94+
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
95+
{
96+
QTextStream myQTextStream( &myFile );
97+
myQTextStream << mReport;
98+
myFile.close();
99+
}
100+
}
101+
102+
void TestQgsImageOperation::init()
103+
{
104+
105+
}
106+
107+
void TestQgsImageOperation::cleanup()
108+
{
109+
110+
}
111+
112+
void TestQgsImageOperation::smallImageOp()
113+
{
114+
QImage image( QString( TEST_DATA_DIR ) + QDir::separator() + "small_sample_image.png" );
115+
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleLightness );
116+
117+
bool result = imageCheck( QString( "imageop_smallimage" ), image, 0 );
118+
QVERIFY( result );
119+
}
120+
121+
void TestQgsImageOperation::grayscaleLightness()
122+
{
123+
QImage image( mSampleImage );
124+
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleLightness );
125+
126+
bool result = imageCheck( QString( "imageop_graylightness" ), image, 0 );
127+
QVERIFY( result );
128+
}
129+
130+
void TestQgsImageOperation::grayscaleLuminosity()
131+
{
132+
QImage image( mSampleImage );
133+
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleLuminosity );
134+
135+
bool result = imageCheck( QString( "imageop_grayluminosity" ), image, 0 );
136+
QVERIFY( result );
137+
}
138+
139+
void TestQgsImageOperation::grayscaleAverage()
140+
{
141+
QImage image( mSampleImage );
142+
QgsImageOperation::convertToGrayscale( image, QgsImageOperation::GrayscaleAverage );
143+
144+
bool result = imageCheck( QString( "imageop_grayaverage" ), image, 0 );
145+
QVERIFY( result );
146+
}
147+
148+
void TestQgsImageOperation::brightnessContrastNoChange()
149+
{
150+
QImage image( mSampleImage );
151+
QgsImageOperation::adjustBrightnessContrast( image, 0, 1.0 );
152+
153+
bool result = imageCheck( QString( "imageop_bcnochange" ), image, 0 );
154+
QVERIFY( result );
155+
}
156+
157+
void TestQgsImageOperation::increaseBrightness()
158+
{
159+
QImage image( mSampleImage );
160+
QgsImageOperation::adjustBrightnessContrast( image, 50, 1.0 );
161+
162+
bool result = imageCheck( QString( "imageop_increasebright" ), image, 0 );
163+
QVERIFY( result );
164+
}
165+
166+
void TestQgsImageOperation::decreaseBrightness()
167+
{
168+
QImage image( mSampleImage );
169+
QgsImageOperation::adjustBrightnessContrast( image, -50, 1.0 );
170+
171+
bool result = imageCheck( QString( "imageop_decreasebright" ), image, 0 );
172+
QVERIFY( result );
173+
}
174+
175+
void TestQgsImageOperation::increaseContrast()
176+
{
177+
QImage image( mSampleImage );
178+
QgsImageOperation::adjustBrightnessContrast( image, 0, 30.0 );
179+
180+
bool result = imageCheck( QString( "imageop_increasecontrast" ), image, 0 );
181+
QVERIFY( result );
182+
}
183+
184+
void TestQgsImageOperation::decreaseContrast()
185+
{
186+
QImage image( mSampleImage );
187+
QgsImageOperation::adjustBrightnessContrast( image, 0, 0.1 );
188+
189+
bool result = imageCheck( QString( "imageop_decreasecontrast" ), image, 0 );
190+
QVERIFY( result );
191+
}
192+
193+
void TestQgsImageOperation::hueSaturationNoChange()
194+
{
195+
QImage image( mSampleImage );
196+
QgsImageOperation::adjustHueSaturation( image, 1.0 );
197+
198+
bool result = imageCheck( QString( "imageop_satnochange" ), image, 0 );
199+
QVERIFY( result );
200+
}
201+
202+
void TestQgsImageOperation::increaseSaturation()
203+
{
204+
QImage image( mSampleImage );
205+
QgsImageOperation::adjustHueSaturation( image, 5.0 );
206+
207+
bool result = imageCheck( QString( "imageop_increasesat" ), image, 0 );
208+
QVERIFY( result );
209+
}
210+
211+
void TestQgsImageOperation::decreaseSaturation()
212+
{
213+
QImage image( mSampleImage );
214+
QgsImageOperation::adjustHueSaturation( image, 0.5 );
215+
216+
bool result = imageCheck( QString( "imageop_decreasesat" ), image, 0 );
217+
QVERIFY( result );
218+
}
219+
220+
void TestQgsImageOperation::colorizeFull()
221+
{
222+
QImage image( mSampleImage );
223+
QgsImageOperation::adjustHueSaturation( image, 1.0, QColor( 255, 255, 0 ), 1.0 );
224+
225+
bool result = imageCheck( QString( "imageop_colorizefull" ), image, 0 );
226+
QVERIFY( result );
227+
}
228+
229+
void TestQgsImageOperation::colorizePartial()
230+
{
231+
QImage image( mSampleImage );
232+
QgsImageOperation::adjustHueSaturation( image, 1.0, QColor( 255, 255, 0 ), 0.5 );
233+
234+
bool result = imageCheck( QString( "imageop_colorizepartial" ), image, 0 );
235+
QVERIFY( result );
236+
}
237+
238+
void TestQgsImageOperation::opacityNoChange()
239+
{
240+
QImage image( mSampleImage );
241+
QgsImageOperation::multiplyOpacity( image, 1.0 );
242+
243+
bool result = imageCheck( QString( "imageop_opacitynochange" ), image, 0 );
244+
QVERIFY( result );
245+
}
246+
247+
void TestQgsImageOperation::opacityIncrease()
248+
{
249+
QImage image( mSampleImage );
250+
QgsImageOperation::multiplyOpacity( image, 2.0 );
251+
252+
bool result = imageCheck( QString( "imageop_opacityincrease" ), image, 0 );
253+
QVERIFY( result );
254+
}
255+
256+
void TestQgsImageOperation::opacityDecrease()
257+
{
258+
QImage image( mSampleImage );
259+
QgsImageOperation::multiplyOpacity( image, 0.5 );
260+
261+
bool result = imageCheck( QString( "imageop_opacitydecrease" ), image, 0 );
262+
QVERIFY( result );
263+
}
264+
265+
void TestQgsImageOperation::overlayColor()
266+
{
267+
QImage image( mSampleImage );
268+
QgsImageOperation::overlayColor( image, QColor( 0, 255, 255 ) );
269+
270+
bool result = imageCheck( QString( "imageop_overlaycolor" ), image, 0 );
271+
QVERIFY( result );
272+
}
273+
274+
void TestQgsImageOperation::distanceTransformMaxDist()
275+
{
276+
QImage image( mSampleImage );
277+
QgsVectorGradientColorRampV2 ramp;
278+
QgsImageOperation::DistanceTransformProperties props;
279+
props.useMaxDistance = true;
280+
props.ramp = &ramp;
281+
props.shadeExterior = true;
282+
283+
QgsImageOperation::distanceTransform( image, props );
284+
285+
bool result = imageCheck( QString( "imageop_dt_max" ), image, 0 );
286+
QVERIFY( result );
287+
}
288+
289+
void TestQgsImageOperation::distanceTransformSetSpread()
290+
{
291+
QImage image( mSampleImage );
292+
QgsVectorGradientColorRampV2 ramp;
293+
QgsImageOperation::DistanceTransformProperties props;
294+
props.useMaxDistance = false;
295+
props.spread = 10;
296+
props.ramp = &ramp;
297+
props.shadeExterior = true;
298+
299+
QgsImageOperation::distanceTransform( image, props );
300+
301+
bool result = imageCheck( QString( "imageop_dt_spread" ), image, 0 );
302+
QVERIFY( result );
303+
}
304+
305+
void TestQgsImageOperation::distanceTransformInterior()
306+
{
307+
QImage image( mSampleImage );
308+
QgsVectorGradientColorRampV2 ramp;
309+
QgsImageOperation::DistanceTransformProperties props;
310+
props.useMaxDistance = true;
311+
props.ramp = &ramp;
312+
props.shadeExterior = false;
313+
314+
QgsImageOperation::distanceTransform( image, props );
315+
316+
bool result = imageCheck( QString( "imageop_dt_interior" ), image, 0 );
317+
QVERIFY( result );
318+
}
319+
320+
void TestQgsImageOperation::stackBlur()
321+
{
322+
QImage image( mSampleImage );
323+
QgsImageOperation::stackBlur( image, 10 );
324+
325+
bool result = imageCheck( QString( "imageop_stackblur" ), image, 0 );
326+
QVERIFY( result );
327+
}
328+
329+
void TestQgsImageOperation::alphaOnlyBlur()
330+
{
331+
QImage image( QString( TEST_DATA_DIR ) + QDir::separator() + "small_sample_image.png" );
332+
QgsImageOperation::stackBlur( image, 10, true );
333+
334+
bool result = imageCheck( QString( "imageop_stackblur_alphaonly" ), image, 0 );
335+
QVERIFY( result );
336+
}
337+
338+
void TestQgsImageOperation::flipHorizontal()
339+
{
340+
QImage image( mSampleImage );
341+
QgsImageOperation::flipImage( image, QgsImageOperation::FlipHorizontal );
342+
343+
bool result = imageCheck( QString( "imageop_fliphoz" ), image, 0 );
344+
QVERIFY( result );
345+
}
346+
347+
void TestQgsImageOperation::flipVertical()
348+
{
349+
QImage image( mSampleImage );
350+
QgsImageOperation::flipImage( image, QgsImageOperation::FlipVertical );
351+
352+
bool result = imageCheck( QString( "imageop_flipvert" ), image, 0 );
353+
QVERIFY( result );
354+
}
355+
356+
//
357+
// Private helper functions not called directly by CTest
358+
//
359+
360+
bool TestQgsImageOperation::imageCheck( QString testName, QImage &image, int mismatchCount )
361+
{
362+
//draw background
363+
QImage imageWithBackground( image.width(), image.height(), QImage::Format_RGB32 );
364+
QgsRenderChecker::drawBackground( &imageWithBackground );
365+
QPainter painter( &imageWithBackground );
366+
painter.drawImage( 0, 0, image );
367+
painter.end();
368+
369+
mReport += "<h2>" + testName + "</h2>\n";
370+
QString tempDir = QDir::tempPath() + QDir::separator();
371+
QString fileName = tempDir + testName + ".png";
372+
imageWithBackground.save( fileName, "PNG" );
373+
QgsRenderChecker checker;
374+
checker.setControlName( "expected_" + testName );
375+
checker.setRenderedImage( fileName );
376+
checker.setColorTolerance( 1 );
377+
bool resultFlag = checker.compareImages( testName, mismatchCount );
378+
mReport += checker.report();
379+
return resultFlag;
380+
}
381+
382+
QTEST_MAIN( TestQgsImageOperation )
383+
#include "testqgsimageoperation.moc"
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

‎tests/testdata/small_sample_image.png

5.56 KB
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.