File indexing completed on 2025-01-05 03:58:43
0001 // SPDX-FileCopyrightText: 2010 Jens-Michael Hoffmann <jmho@c-xx.com> 0002 // 0003 // SPDX-License-Identifier: LGPL-2.1-or-later 0004 0005 #include "BlendingAlgorithms.h" 0006 0007 #include "TextureTile.h" 0008 0009 #include <cmath> 0010 0011 #include <QImage> 0012 #include <QPainter> 0013 0014 namespace Marble 0015 { 0016 0017 void OverpaintBlending::blend( QImage * const bottom, TextureTile const * const top ) const 0018 { 0019 Q_ASSERT( bottom ); 0020 Q_ASSERT( top ); 0021 Q_ASSERT( top->image() ); 0022 Q_ASSERT( bottom->size() == top->image()->size() ); 0023 Q_ASSERT( bottom->format() == QImage::Format_ARGB32_Premultiplied ); 0024 0025 QPainter painter( bottom ); 0026 0027 painter.drawImage( 0, 0, *top->image() ); 0028 } 0029 0030 void GrayscaleBlending::blend( QImage * const bottom, TextureTile const * const top ) const 0031 { 0032 Q_ASSERT( bottom ); 0033 Q_ASSERT( top ); 0034 Q_ASSERT( top->image() ); 0035 Q_ASSERT( bottom->size() == top->image()->size() ); 0036 Q_ASSERT( bottom->format() == QImage::Format_ARGB32_Premultiplied ); 0037 QImage const topImagePremult = top->image()->convertToFormat( QImage::Format_ARGB32_Premultiplied ); 0038 0039 // Draw a grayscale version of the bottom image 0040 int const width = bottom->width(); 0041 int const height = bottom->height(); 0042 0043 for ( int y = 0; y < height; ++y ) { 0044 for ( int x = 0; x < width; ++x ) { 0045 QRgb const topPixel = topImagePremult.pixel( x, y ); 0046 int const gray = qGray( topPixel ); 0047 QRgb const grayPixel = qRgb( gray, gray, gray ); 0048 bottom->setPixel( x, y,grayPixel ); 0049 } 0050 } 0051 0052 } 0053 0054 void InvertColorBlending::blend( QImage * const bottom, TextureTile const * const top ) const 0055 { 0056 Q_ASSERT( bottom ); 0057 Q_ASSERT( top ); 0058 Q_ASSERT( top->image() ); 0059 Q_ASSERT( bottom->size() == top->image()->size() ); 0060 Q_ASSERT( bottom->format() == QImage::Format_ARGB32_Premultiplied ); 0061 QImage const topImagePremult = top->image()->convertToFormat( QImage::Format_ARGB32_Premultiplied ); 0062 0063 // Draw an inverted version of the bottom image 0064 int const width = bottom->width(); 0065 int const height = bottom->height(); 0066 0067 for ( int y = 0; y < height; ++y ) { 0068 for ( int x = 0; x < width; ++x ) { 0069 QRgb const topPixel = topImagePremult.pixel( x, y ); 0070 QRgb const invertedPixel = qRgb( 255 - qRed(topPixel), 255 - qGreen(topPixel), 255 - qBlue(topPixel) ); 0071 bottom->setPixel( x, y, invertedPixel ); 0072 } 0073 } 0074 } 0075 0076 void InvertHueBlending::blend( QImage * const bottom, TextureTile const * const top ) const 0077 { 0078 Q_ASSERT( bottom ); 0079 Q_ASSERT( top ); 0080 Q_ASSERT( top->image() ); 0081 Q_ASSERT( bottom->size() == top->image()->size() ); 0082 Q_ASSERT( bottom->format() == QImage::Format_ARGB32_Premultiplied ); 0083 QImage const topImagePremult = top->image()->convertToFormat( QImage::Format_ARGB32_Premultiplied ); 0084 0085 // Draw an inverted version of the bottom image 0086 int const width = bottom->width(); 0087 int const height = bottom->height(); 0088 0089 for ( int y = 0; y < height; ++y ) { 0090 for ( int x = 0; x < width; ++x ) { 0091 QRgb const topPixel = topImagePremult.pixel( x, y ); 0092 QColor invertedColor(255 - qRed(topPixel), 255 - qGreen(topPixel), 255 - qBlue(topPixel)); 0093 int hue = invertedColor.hslHue(); 0094 int saturation = invertedColor.hslSaturation(); 0095 int lightness = invertedColor.lightness(); 0096 QColor naturalInvertedColor = QColor::fromHsl((hue + 195) % 255 , saturation, lightness); 0097 QRgb naturalInvertedPixel = naturalInvertedColor.rgb(); 0098 bottom->setPixel( x, y, naturalInvertedPixel ); 0099 } 0100 } 0101 } 0102 0103 // pre-conditions: 0104 // - bottom and top image have the same size 0105 // - bottom image format is ARGB32_Premultiplied 0106 void IndependentChannelBlending::blend( QImage * const bottom, 0107 TextureTile const * const top ) const 0108 { 0109 QImage const * const topImage = top->image(); 0110 Q_ASSERT( topImage ); 0111 Q_ASSERT( bottom->size() == topImage->size() ); 0112 Q_ASSERT( bottom->format() == QImage::Format_ARGB32_Premultiplied ); 0113 0114 int const width = bottom->width(); 0115 int const height = bottom->height(); 0116 QImage const topImagePremult = topImage->convertToFormat( QImage::Format_ARGB32_Premultiplied ); 0117 for ( int y = 0; y < height; ++y ) { 0118 for ( int x = 0; x < width; ++x ) { 0119 QRgb const bottomPixel = bottom->pixel( x, y ); 0120 QRgb const topPixel = topImagePremult.pixel( x, y ); 0121 qreal const resultRed = blendChannel( qRed( bottomPixel ) / 255.0, 0122 qRed( topPixel ) / 255.0 ); 0123 qreal const resultGreen = blendChannel( qGreen( bottomPixel ) / 255.0, 0124 qGreen( topPixel ) / 255.0 ); 0125 qreal const resultBlue = blendChannel( qBlue( bottomPixel ) / 255.0, 0126 qBlue( topPixel ) / 255.0 ); 0127 bottom->setPixel( x, y, qRgb( resultRed * 255.0, 0128 resultGreen * 255.0, 0129 resultBlue * 255.0 )); 0130 } 0131 } 0132 } 0133 0134 0135 // Neutral blendings 0136 0137 qreal AllanonBlending::blendChannel( qreal const bottomColorIntensity, 0138 qreal const topColorIntensity ) const 0139 { 0140 return ( bottomColorIntensity + topColorIntensity ) / 2.0; 0141 } 0142 0143 qreal ArcusTangentBlending::blendChannel( qreal const bottomColorIntensity, 0144 qreal const topColorIntensity ) const 0145 { 0146 return 2.0 * atan( topColorIntensity / bottomColorIntensity ) / M_PI; 0147 } 0148 0149 qreal GeometricMeanBlending::blendChannel( qreal const bottomColorIntensity, 0150 qreal const topColorIntensity ) const 0151 { 0152 return sqrt( bottomColorIntensity * topColorIntensity ); 0153 } 0154 0155 qreal LinearLightBlending::blendChannel( qreal const bottomColorIntensity, 0156 qreal const topColorIntensity ) const 0157 { 0158 return qMin( qreal( 1.0 ), 0159 qMax( qreal( 0.0 ), qreal( bottomColorIntensity + 2.0 * topColorIntensity - 1.0 ))); 0160 } 0161 0162 qreal OverlayBlending::blendChannel( qreal const bottomColorIntensity, 0163 qreal const topColorIntensity ) const 0164 { 0165 if ( bottomColorIntensity < 0.5 ) 0166 return 2.0 * bottomColorIntensity * topColorIntensity; 0167 else 0168 return 1.0 - 2.0 * ( 1.0 - bottomColorIntensity ) * ( 1.0 - topColorIntensity ); 0169 } 0170 0171 qreal ParallelBlending::blendChannel( qreal const bottomColorIntensity, 0172 qreal const topColorIntensity ) const 0173 { 0174 Q_UNUSED(bottomColorIntensity); 0175 Q_UNUSED(topColorIntensity); 0176 // FIXME: return qMin( qMax( 2.0 / ( 1.0 / bottomColorIntensity + 1.0 / topColorIntensity )), 0.0, 1.0 ); 0177 return 0.0; 0178 } 0179 0180 qreal TextureBlending::blendChannel( qreal const bottomColorIntensity, 0181 qreal const topColorIntensity ) const 0182 { 0183 Q_UNUSED(bottomColorIntensity); 0184 Q_UNUSED(topColorIntensity); 0185 // FIXME: return qMax( qMin( topColorIntensity + bottomColorIntensity ) - 0.5 ), 1.0 ), 0.0 ); 0186 return 0.0; 0187 } 0188 0189 0190 // Darkening blendings 0191 0192 qreal ColorBurnBlending::blendChannel( qreal const bottomColorIntensity, 0193 qreal const topColorIntensity ) const 0194 { 0195 Q_UNUSED(bottomColorIntensity); 0196 Q_UNUSED(topColorIntensity); 0197 // FIXME: check if this formula makes sense 0198 return qMin( qreal( 1.0 ), 0199 qMax( qreal( 0.0 ), qreal( 1.0 - ( 1.0 - bottomColorIntensity ) / topColorIntensity ))); 0200 } 0201 0202 qreal DarkBlending::blendChannel( qreal const bottomColorIntensity, 0203 qreal const topColorIntensity ) const 0204 { 0205 return ( bottomColorIntensity + 1.0 - topColorIntensity ) * topColorIntensity; 0206 } 0207 0208 qreal DarkenBlending::blendChannel( qreal const bottomColorIntensity, 0209 qreal const topColorIntensity ) const 0210 { 0211 // FIXME: is this really ok? not vice versa? 0212 return bottomColorIntensity > topColorIntensity ? topColorIntensity : bottomColorIntensity; 0213 } 0214 0215 qreal DivideBlending::blendChannel( qreal const bottomColorIntensity, 0216 qreal const topColorIntensity ) const 0217 { 0218 return log1p( bottomColorIntensity / ( 1.0 - topColorIntensity ) / 8.0) / log(2.0); 0219 } 0220 0221 qreal GammaDarkBlending::blendChannel( qreal const bottomColorIntensity, 0222 qreal const topColorIntensity ) const 0223 { 0224 return pow( bottomColorIntensity, 1.0 / topColorIntensity ); 0225 } 0226 0227 qreal LinearBurnBlending::blendChannel( qreal const bottomColorIntensity, 0228 qreal const topColorIntensity ) const 0229 { 0230 return qMax( qreal(0.0), bottomColorIntensity + topColorIntensity - qreal( 1.0 ) ); 0231 } 0232 0233 qreal MultiplyBlending::blendChannel( qreal const bottomColorIntensity, 0234 qreal const topColorIntensity ) const 0235 { 0236 return bottomColorIntensity * topColorIntensity; 0237 } 0238 0239 qreal SubtractiveBlending::blendChannel( qreal const bottomColorIntensity, 0240 qreal const topColorIntensity ) const 0241 { 0242 return qMax( bottomColorIntensity - topColorIntensity, qreal(0.0) ); 0243 } 0244 0245 0246 // Lightening blendings 0247 0248 qreal AdditiveBlending::blendChannel( qreal const bottomColorIntensity, 0249 qreal const topColorIntensity ) const 0250 { 0251 return qMin( topColorIntensity + bottomColorIntensity, qreal(1.0) ); 0252 } 0253 0254 qreal ColorDodgeBlending::blendChannel( qreal const bottomColorIntensity, 0255 qreal const topColorIntensity ) const 0256 { 0257 return qMin( qreal( 1.0 ), 0258 qMax( qreal( 0.0 ), qreal( bottomColorIntensity / ( 1.0 - topColorIntensity )))); 0259 } 0260 0261 qreal GammaLightBlending::blendChannel( qreal const bottomColorIntensity, 0262 qreal const topColorIntensity ) const 0263 { 0264 return pow( bottomColorIntensity, topColorIntensity ); 0265 } 0266 0267 qreal HardLightBlending::blendChannel( qreal const bottomColorIntensity, 0268 qreal const topColorIntensity ) const 0269 { 0270 return topColorIntensity < 0.5 0271 ? 2.0 * bottomColorIntensity * topColorIntensity 0272 : 1.0 - 2.0 * ( 1.0 - bottomColorIntensity ) * ( 1.0 - topColorIntensity ); 0273 } 0274 0275 qreal LightBlending::blendChannel( qreal const bottomColorIntensity, 0276 qreal const topColorIntensity ) const 0277 { 0278 return bottomColorIntensity * ( 1.0 - topColorIntensity ) + pow( topColorIntensity, 2 ); 0279 } 0280 0281 qreal LightenBlending::blendChannel( qreal const bottomColorIntensity, 0282 qreal const topColorIntensity ) const 0283 { 0284 // is this ok? 0285 return bottomColorIntensity < topColorIntensity ? topColorIntensity : bottomColorIntensity; 0286 } 0287 0288 qreal PinLightBlending::blendChannel( qreal const bottomColorIntensity, 0289 qreal const topColorIntensity ) const 0290 { 0291 return qMax( qreal(0.0), qMax( qreal(2.0 + topColorIntensity - 1.0), 0292 qMin( bottomColorIntensity, qreal(2.0 * topColorIntensity )))); 0293 } 0294 0295 qreal ScreenBlending::blendChannel( qreal const bottomColorIntensity, 0296 qreal const topColorIntensity ) const 0297 { 0298 return 1.0 - ( 1.0 - bottomColorIntensity ) * ( 1.0 - topColorIntensity ); 0299 } 0300 0301 qreal SoftLightBlending::blendChannel( qreal const bottomColorIntensity, 0302 qreal const topColorIntensity ) const 0303 { 0304 return pow( bottomColorIntensity, pow( 2.0, ( 2.0 * ( 0.5 - topColorIntensity )))); 0305 } 0306 0307 qreal VividLightBlending::blendChannel( qreal const bottomColorIntensity, 0308 qreal const topColorIntensity ) const 0309 { 0310 return topColorIntensity < 0.5 0311 ? qMin( qreal( 1.0 ), 0312 qMax( qreal( 0.0 ), qreal( 1.0 - ( 1.0 - bottomColorIntensity ) / ( 2.0 * topColorIntensity )))) 0313 : qMin( qreal( 1.0 ), 0314 qMax( qreal( 0.0 ), qreal( bottomColorIntensity / ( 2.0 * ( 1.0 - topColorIntensity ))))); 0315 } 0316 0317 0318 // Inverter blendings 0319 0320 qreal AdditiveSubtractiveBlending::blendChannel( qreal const bottomColorIntensity, 0321 qreal const topColorIntensity ) const 0322 { 0323 Q_UNUSED(bottomColorIntensity); 0324 Q_UNUSED(topColorIntensity); 0325 // FIXME: 0326 // return qMin( 1.0, qMax( 0.0, abs( bottomColorIntensity * bottomColorIntensity 0327 // - topColorIntensity * topColorIntensity ))); 0328 return 0.0; 0329 } 0330 0331 qreal BleachBlending::blendChannel( qreal const bottomColorIntensity, 0332 qreal const topColorIntensity ) const 0333 { 0334 // FIXME: "why this is the same formula as Screen Blending? Please correct.)" 0335 return 1.0 - ( 1.0 - bottomColorIntensity ) * ( 1.0 - topColorIntensity ); 0336 } 0337 0338 qreal DifferenceBlending::blendChannel( qreal const bottomColorIntensity, 0339 qreal const topColorIntensity ) const 0340 { 0341 return qMax( qMin( qreal( 1.0 ), qreal( bottomColorIntensity - topColorIntensity + 0.5 )), 0342 qreal( 0.0 )); 0343 } 0344 0345 qreal EquivalenceBlending::blendChannel( qreal const bottomColorIntensity, 0346 qreal const topColorIntensity ) const 0347 { 0348 return 1.0 - qAbs( bottomColorIntensity - topColorIntensity ); 0349 } 0350 0351 qreal HalfDifferenceBlending::blendChannel( qreal const bottomColorIntensity, 0352 qreal const topColorIntensity ) const 0353 { 0354 return bottomColorIntensity + topColorIntensity 0355 - 2.0 * ( bottomColorIntensity * topColorIntensity ); 0356 } 0357 0358 0359 // Special purpose blendings 0360 0361 void CloudsBlending::blend( QImage * const bottom, TextureTile const * const top ) const 0362 { 0363 QImage const * const topImage = top->image(); 0364 Q_ASSERT( topImage ); 0365 Q_ASSERT( bottom->size() == topImage->size() ); 0366 int const width = bottom->width(); 0367 int const height = bottom->height(); 0368 for ( int y = 0; y < height; ++y ) { 0369 for ( int x = 0; x < width; ++x ) { 0370 qreal const c = qRed( topImage->pixel( x, y )) / 255.0; 0371 QRgb const bottomPixel = bottom->pixel( x, y ); 0372 int const bottomRed = qRed( bottomPixel ); 0373 int const bottomGreen = qGreen( bottomPixel ); 0374 int const bottomBlue = qBlue( bottomPixel ); 0375 bottom->setPixel( x, y, qRgb(( int )( bottomRed + ( 255 - bottomRed ) * c ), 0376 ( int )( bottomGreen + ( 255 - bottomGreen ) * c ), 0377 ( int )( bottomBlue + ( 255 - bottomBlue ) * c ))); 0378 } 0379 } 0380 } 0381 0382 0383 }