1 /***************************************************************************
2 qgsbrightnesscontrastfilter.cpp
3 ---------------------
4 begin : February 2013
5 copyright : (C) 2013 by Alexander Bruy
6 email : alexander dot bruy 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 "qgsrasterdataprovider.h"
19 #include "qgsbrightnesscontrastfilter.h"
20
21 #include <QDomDocument>
22 #include <QDomElement>
23
QgsBrightnessContrastFilter(QgsRasterInterface * input)24 QgsBrightnessContrastFilter::QgsBrightnessContrastFilter( QgsRasterInterface *input )
25 : QgsRasterInterface( input )
26 {
27 }
28
clone() const29 QgsBrightnessContrastFilter *QgsBrightnessContrastFilter::clone() const
30 {
31 QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
32 QgsBrightnessContrastFilter *filter = new QgsBrightnessContrastFilter( nullptr );
33 filter->setBrightness( mBrightness );
34 filter->setContrast( mContrast );
35 filter->setGamma( mGamma );
36 return filter;
37 }
38
bandCount() const39 int QgsBrightnessContrastFilter::bandCount() const
40 {
41 if ( mOn )
42 {
43 return 1;
44 }
45
46 if ( mInput )
47 {
48 return mInput->bandCount();
49 }
50
51 return 0;
52 }
53
dataType(int bandNo) const54 Qgis::DataType QgsBrightnessContrastFilter::dataType( int bandNo ) const
55 {
56 if ( mOn )
57 {
58 return Qgis::DataType::ARGB32_Premultiplied;
59 }
60
61 if ( mInput )
62 {
63 return mInput->dataType( bandNo );
64 }
65
66 return Qgis::DataType::UnknownDataType;
67 }
68
setInput(QgsRasterInterface * input)69 bool QgsBrightnessContrastFilter::setInput( QgsRasterInterface *input )
70 {
71 QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
72
73 // Brightness filter can only work with single band ARGB32_Premultiplied
74 if ( !input )
75 {
76 QgsDebugMsgLevel( QStringLiteral( "No input" ), 4 );
77 return false;
78 }
79
80 if ( !mOn )
81 {
82 // In off mode we can connect to anything
83 QgsDebugMsgLevel( QStringLiteral( "OK" ), 4 );
84 mInput = input;
85 return true;
86 }
87
88 if ( input->bandCount() < 1 )
89 {
90 QgsDebugMsg( QStringLiteral( "No input band" ) );
91 return false;
92 }
93
94 if ( input->dataType( 1 ) != Qgis::DataType::ARGB32_Premultiplied &&
95 input->dataType( 1 ) != Qgis::DataType::ARGB32 )
96 {
97 QgsDebugMsg( QStringLiteral( "Unknown input data type" ) );
98 return false;
99 }
100
101 mInput = input;
102 QgsDebugMsgLevel( QStringLiteral( "OK" ), 4 );
103 return true;
104 }
105
block(int bandNo,QgsRectangle const & extent,int width,int height,QgsRasterBlockFeedback * feedback)106 QgsRasterBlock *QgsBrightnessContrastFilter::block( int bandNo, QgsRectangle const &extent, int width, int height, QgsRasterBlockFeedback *feedback )
107 {
108 Q_UNUSED( bandNo )
109 QgsDebugMsgLevel( QStringLiteral( "width = %1 height = %2 extent = %3" ).arg( width ).arg( height ).arg( extent.toString() ), 4 );
110
111 std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
112 if ( !mInput )
113 {
114 return outputBlock.release();
115 }
116
117 // At this moment we know that we read rendered image
118 int bandNumber = 1;
119 std::unique_ptr< QgsRasterBlock > inputBlock( mInput->block( bandNumber, extent, width, height, feedback ) );
120 if ( !inputBlock || inputBlock->isEmpty() )
121 {
122 QgsDebugMsg( QStringLiteral( "No raster data!" ) );
123 return outputBlock.release();
124 }
125
126 if ( mBrightness == 0 && mContrast == 0 && mGamma == 1.0 )
127 {
128 QgsDebugMsgLevel( QStringLiteral( "No brightness/contrast/gamma changes." ), 4 );
129 return inputBlock.release();
130 }
131
132 if ( !outputBlock->reset( Qgis::DataType::ARGB32_Premultiplied, width, height ) )
133 {
134 return outputBlock.release();
135 }
136
137 // adjust image
138 QRgb myNoDataColor = qRgba( 0, 0, 0, 0 );
139 QRgb myColor;
140
141 int r, g, b, alpha;
142 double f = std::pow( ( mContrast + 100 ) / 100.0, 2 );
143 double gammaCorrection = 1.0 / mGamma;
144
145 for ( qgssize i = 0; i < ( qgssize )width * height; i++ )
146 {
147 if ( inputBlock->color( i ) == myNoDataColor )
148 {
149 outputBlock->setColor( i, myNoDataColor );
150 continue;
151 }
152
153 myColor = inputBlock->color( i );
154 alpha = qAlpha( myColor );
155
156 r = adjustColorComponent( qRed( myColor ), alpha, mBrightness, f, gammaCorrection );
157 g = adjustColorComponent( qGreen( myColor ), alpha, mBrightness, f, gammaCorrection );
158 b = adjustColorComponent( qBlue( myColor ), alpha, mBrightness, f, gammaCorrection );
159
160 outputBlock->setColor( i, qRgba( r, g, b, alpha ) );
161 }
162
163 return outputBlock.release();
164 }
165
setBrightness(int brightness)166 void QgsBrightnessContrastFilter::setBrightness( int brightness )
167 {
168 mBrightness = std::clamp( brightness, -255, 255 );
169 }
170
setContrast(int contrast)171 void QgsBrightnessContrastFilter::setContrast( int contrast )
172 {
173 mContrast = std::clamp( contrast, -100, 100 );
174 }
175
setGamma(double gamma)176 void QgsBrightnessContrastFilter::setGamma( double gamma )
177 {
178 mGamma = std::clamp( gamma, 0.1, 10.0 );
179 }
180
adjustColorComponent(int colorComponent,int alpha,int brightness,double contrastFactor,double gammaCorrection) const181 int QgsBrightnessContrastFilter::adjustColorComponent( int colorComponent, int alpha, int brightness, double contrastFactor, double gammaCorrection ) const
182 {
183 if ( alpha == 255 )
184 {
185 // Opaque pixel, do simpler math
186 return std::clamp( ( int )( 255 * std::pow( ( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ) / 255.0, gammaCorrection ) ), 0, 255 );
187 }
188 else if ( alpha == 0 )
189 {
190 // Totally transparent pixel
191 return 0;
192 }
193 else
194 {
195 // Semi-transparent pixel. We need to adjust the math since we are using Qgis::DataType::ARGB32_Premultiplied
196 // and color values have been premultiplied by alpha
197 double alphaFactor = alpha / 255.;
198 double adjustedColor = colorComponent / alphaFactor;
199
200 // Make sure to return a premultiplied color
201 return alphaFactor * std::clamp( 255 * std::pow( ( ( ( ( ( ( adjustedColor / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ) / 255, gammaCorrection ), 0., 255. );
202 }
203 }
204
writeXml(QDomDocument & doc,QDomElement & parentElem) const205 void QgsBrightnessContrastFilter::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
206 {
207 if ( parentElem.isNull() )
208 {
209 return;
210 }
211
212 QDomElement filterElem = doc.createElement( QStringLiteral( "brightnesscontrast" ) );
213
214 filterElem.setAttribute( QStringLiteral( "brightness" ), QString::number( mBrightness ) );
215 filterElem.setAttribute( QStringLiteral( "contrast" ), QString::number( mContrast ) );
216 filterElem.setAttribute( QStringLiteral( "gamma" ), QString::number( mGamma ) );
217 parentElem.appendChild( filterElem );
218 }
219
readXml(const QDomElement & filterElem)220 void QgsBrightnessContrastFilter::readXml( const QDomElement &filterElem )
221 {
222 if ( filterElem.isNull() )
223 {
224 return;
225 }
226
227 mBrightness = filterElem.attribute( QStringLiteral( "brightness" ), QStringLiteral( "0" ) ).toInt();
228 mContrast = filterElem.attribute( QStringLiteral( "contrast" ), QStringLiteral( "0" ) ).toInt();
229 mGamma = filterElem.attribute( QStringLiteral( "gamma" ), QStringLiteral( "1" ) ).toDouble();
230 }
231