File indexing completed on 2024-05-05 03:49:13

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 }