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