File indexing completed on 2024-04-28 05:32:10

0001 /*
0002     this file is part of the oxygen gtk engine
0003     SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0004 
0005     inspired notably from kdelibs/kdeui/color/kcolorutils.h
0006     SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
0007     SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
0008     SPDX-FileCopyrightText: 2007 Zack Rusin <zack@kde.org>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 
0013 #include "oxygencolorutils.h"
0014 #include "oxygencache.h"
0015 #include "oxygenrgba.h"
0016 
0017 #include <algorithm>
0018 #include <math.h>
0019 #include <map>
0020 #include <sstream>
0021 
0022 namespace Oxygen
0023 {
0024 
0025     namespace ColorUtils
0026     {
0027 
0028         //___________________________________________________________
0029         // contrast
0030         static double _contrast = 0.5;
0031         static double _bgcontrast = 0.5;
0032 
0033         //___________________________________________________________
0034         void setContrast( double value )
0035         {
0036             _contrast = value;
0037             _bgcontrast = std::min( 1.0, 0.9*_contrast/0.7 );
0038         }
0039 
0040         //___________________________________________________________
0041         const double& contrast( void )
0042         { return _contrast; }
0043 
0044         //___________________________________________________________
0045         const double& backgroundContrast( void )
0046         { return _bgcontrast; }
0047 
0048         //___________________________________________________________________
0049         static inline double normalize( double a )
0050         { return ( a < 1.0 ? ( a > 0.0 ? a : 0.0 ) : 1.0 ); }
0051 
0052         // caches
0053         typedef SimpleCache<guint32,Rgba> ColorCache;
0054 
0055         static ColorCache m_decoColorCache;
0056         static ColorCache m_lightColorCache;
0057         static ColorCache m_darkColorCache;
0058         static ColorCache m_midColorCache;
0059         static ColorCache m_shadowColorCache;
0060         static ColorCache m_backgroundTopColorCache;
0061         static ColorCache m_backgroundBottomColorCache;
0062         static ColorCache m_backgroundRadialColorCache;
0063         static ColorCache m_backgroundColorCache;
0064 
0065         typedef SimpleCache<guint32,bool> ColorFlags;
0066         ColorFlags m_highThreshold;
0067         ColorFlags m_lowThreshold;
0068 
0069         // clear caches
0070         void clearCaches( void )
0071         {
0072             m_decoColorCache.clear();
0073             m_lightColorCache.clear();
0074             m_darkColorCache.clear();
0075             m_midColorCache.clear();
0076             m_shadowColorCache.clear();
0077             m_backgroundTopColorCache.clear();
0078             m_backgroundBottomColorCache.clear();
0079             m_backgroundRadialColorCache.clear();
0080             m_backgroundColorCache.clear();
0081 
0082             m_highThreshold.clear();
0083             m_lowThreshold.clear();
0084 
0085         }
0086 
0087     }
0088 
0089     //____________________________________________________________________
0090     bool ColorUtils::lowThreshold(const Rgba &color)
0091     {
0092 
0093         ColorFlags::const_iterator iter( m_lowThreshold.find( color.toInt() ) );
0094         if( iter != m_lowThreshold.end() ) return iter->second;
0095         else {
0096 
0097             const Rgba darker( shade(color, MidShade, 0.5 ) );
0098             const bool out( luma(darker) > luma(color) );
0099             m_lowThreshold.insert( color.toInt(), out );
0100             return out;
0101         }
0102 
0103     }
0104 
0105      //____________________________________________________________________
0106     bool ColorUtils::highThreshold(const Rgba &color)
0107     {
0108 
0109         ColorFlags::const_iterator iter( m_highThreshold.find( color.toInt() ) );
0110         if( iter != m_highThreshold.end() ) return iter->second;
0111         else {
0112 
0113             const Rgba lighter( shade(color, LightShade, 0.5 ) );
0114             const bool out( luma(lighter) < luma(color) );
0115             m_highThreshold.insert( color.toInt(), out );
0116             return out;
0117         }
0118     }
0119 
0120     //_________________________________________________________________________
0121     ColorUtils::Rgba ColorUtils::backgroundTopColor(const Rgba &color)
0122     {
0123 
0124         ColorCache::const_iterator iter( m_backgroundTopColorCache.find( color.toInt() ) );
0125         if( iter != m_backgroundTopColorCache.end() ) return iter->second;
0126         else {
0127             Rgba out;
0128             if( lowThreshold(color) ) out = shade(color, MidlightShade, 0.0);
0129             else {
0130                 const double my( luma( shade(color, LightShade, 0.0) ) );
0131                 const double by( luma(color) );
0132                 out = shade(color, (my - by) * backgroundContrast());
0133             }
0134 
0135             m_backgroundTopColorCache.insert( color.toInt(), out );
0136             return out;
0137         }
0138     }
0139 
0140     //_________________________________________________________________________
0141     ColorUtils::Rgba ColorUtils::backgroundBottomColor(const Rgba &color)
0142     {
0143 
0144         ColorCache::const_iterator iter( m_backgroundBottomColorCache.find( color.toInt() ) );
0145         if( iter != m_backgroundBottomColorCache.end() ) return iter->second;
0146         else {
0147             Rgba out( shade(color, MidShade, 0.0) );
0148             if( !lowThreshold(color) ) {
0149 
0150                 const double by( luma(color) );
0151                 const double my( luma(out) );
0152                 out = shade(color, (my - by) * backgroundContrast());
0153             }
0154 
0155             m_backgroundBottomColorCache.insert( color.toInt(), out );
0156             return out;
0157         }
0158     }
0159 
0160     //_________________________________________________________________________
0161     ColorUtils::Rgba ColorUtils::backgroundRadialColor(const Rgba &color)
0162     {
0163 
0164         ColorCache::const_iterator iter( m_backgroundRadialColorCache.find( color.toInt() ) );
0165         if( iter != m_backgroundRadialColorCache.end() ) return iter->second;
0166         else {
0167             Rgba out;
0168             if( lowThreshold(color) ) out = shade(color, LightShade, 0.0);
0169             else if( highThreshold( color ) ) out = color;
0170             else out = shade(color, LightShade, backgroundContrast() );
0171             m_backgroundRadialColorCache.insert( color.toInt(), out );
0172             return out;
0173         }
0174 
0175     }
0176 
0177     //_________________________________________________________________________
0178     ColorUtils::Rgba ColorUtils::lightColor(const ColorUtils::Rgba &color)
0179     {
0180 
0181         ColorCache::const_iterator iter( m_lightColorCache.find( color.toInt() ) );
0182         if( iter != m_lightColorCache.end() ) return iter->second;
0183         else {
0184             const Rgba out( highThreshold( color ) ? color: shade( color, LightShade, contrast() ) );
0185             m_lightColorCache.insert( color.toInt(), out );
0186             return out;
0187         }
0188     }
0189 
0190     //_________________________________________________________________________
0191     ColorUtils::Rgba ColorUtils::darkColor( const ColorUtils::Rgba& color )
0192     {
0193         ColorCache::const_iterator iter( m_darkColorCache.find( color.toInt() ) );
0194         if( iter != m_darkColorCache.end() ) return iter->second;
0195         else {
0196             const Rgba out( lowThreshold(color) ?
0197                 mix( lightColor(color), color, 0.3 + 0.7 * contrast() ):
0198                 shade(color, MidShade, contrast() ) );
0199             m_darkColorCache.insert( color.toInt(), out );
0200             return out;
0201         }
0202     }
0203 
0204     //_________________________________________________________________________
0205     ColorUtils::Rgba ColorUtils::midColor( const ColorUtils::Rgba& color )
0206     {
0207         ColorCache::const_iterator iter( m_midColorCache.find( color.toInt() ) );
0208         if( iter != m_midColorCache.end() ) return iter->second;
0209         else {
0210             const Rgba out( shade( color, MidShade, contrast() - 1.0 ) );
0211             m_midColorCache.insert( color.toInt(), out );
0212             return out;
0213         }
0214     }
0215 
0216     //_________________________________________________________________________
0217     ColorUtils::Rgba ColorUtils::shadowColor( const ColorUtils::Rgba& color )
0218     {
0219         ColorCache::const_iterator iter( m_shadowColorCache.find( color.toInt() ) );
0220         if( iter != m_shadowColorCache.end() ) return iter->second;
0221         else {
0222 
0223             Rgba out( mix( Rgba::black(), color, color.alpha() ) );
0224             if( !lowThreshold(color) ) out = shade( out, ShadowShade, contrast() );
0225             m_shadowColorCache.insert( color.toInt(), out );
0226             return out;
0227         }
0228     }
0229 
0230     //_________________________________________________________________________
0231     ColorUtils::Rgba ColorUtils::decoColor( const ColorUtils::Rgba& background, const ColorUtils::Rgba& color )
0232     { return mix( background, color, 0.8*(1.0 + contrast() ) ); }
0233 
0234     //_________________________________________________________________________
0235     ColorUtils::Rgba ColorUtils::alphaColor( const ColorUtils::Rgba& color, double alpha )
0236     { return Rgba( color.red(), color.green(), color.blue(), normalize(alpha)*color.alpha() ); }
0237 
0238     //____________________________________________________________________
0239     ColorUtils::Rgba ColorUtils::backgroundColor(const ColorUtils::Rgba &color, double ratio )
0240     {
0241 
0242         if( ratio < 0 ) return color;
0243 
0244         if( ratio < 0.5 )
0245         {
0246 
0247             const double a( 2.0*ratio );
0248             return mix(backgroundTopColor(color), color, a );
0249 
0250         } else {
0251 
0252             const double a( 2.0*ratio-1 );
0253             return mix(color, backgroundBottomColor(color), a );
0254 
0255         }
0256 
0257     }
0258 
0259     namespace ColorUtils
0260     {
0261 
0262         //___________________________________________________________________
0263         // luma coefficient
0264         static const double yc[3] = { 0.2126, 0.7152, 0.0722 };
0265 
0266         //___________________________________________________________________
0267         static inline double mixdouble( double a, double b, double bias )
0268         { return a + ( b - a ) * bias; }
0269 
0270         //___________________________________________________________________
0271         static inline double gamma( double n )
0272         { return pow( normalize( n ), 2.2 ); }
0273 
0274         //___________________________________________________________________
0275         static inline double igamma(double n)
0276         { return pow( normalize(n), 1.0/2.2); }
0277 
0278         //___________________________________________________________________
0279         static inline double wrap( double a, double d = 1.0 )
0280         {
0281             double r = fmod( a, d );
0282             return ( r < 0.0 ? d + r : ( r > 0.0 ? r : 0.0 ) );
0283         }
0284 
0285         //___________________________________________________________________
0286         // hcy representation of a colors
0287         class HCY
0288         {
0289 
0290             public:
0291 
0292             //! constructor
0293             HCY( const Rgba& color )
0294             {
0295 
0296                 // transparency
0297                 a = color.alpha();
0298 
0299                 // luma component
0300                 y = luma( color );
0301 
0302                 double r = gamma( color.red() );
0303                 double g = gamma( color.green() );
0304                 double b = gamma( color.blue() );
0305 
0306                 // hue component
0307                 double p = std::max( std::max( r, g ), b );
0308                 double n = std::min( std::min( r, g ), b );
0309                 double d = 6.0 * ( p - n );
0310                 if( n == p ) h = 0.0;
0311                 else if( r == p ) h = ( ( g - b ) / d );
0312                 else if( g == p ) h = ( ( b - r ) / d ) + ( 1.0 / 3.0 );
0313                 else h = ( ( r - g ) / d ) + ( 2.0 / 3.0 );
0314 
0315                 // chroma component
0316                 if( r == g && g == b ) c = 0.0;
0317                 else c = std::max( ( y - n ) / y, ( p - y ) / ( 1 - y ) );
0318 
0319             }
0320 
0321             //! convert back to color
0322             Rgba rgba() const
0323             {
0324                 // start with sane component values
0325                 double _h = wrap( h );
0326                 double _c = normalize( c );
0327                 double _y = normalize( y );
0328 
0329                 // calculate some needed variables
0330                 double _hs = _h * 6.0, th, tm;
0331                 if( _hs < 1.0 )
0332                 {
0333 
0334                     th = _hs;
0335                     tm = yc[0] + yc[1] * th;
0336 
0337                 } else if( _hs < 2.0 ) {
0338 
0339                     th = 2.0 - _hs;
0340                     tm = yc[1] + yc[0] * th;
0341 
0342                 } else if( _hs < 3.0 ) {
0343 
0344                     th = _hs - 2.0;
0345                     tm = yc[1] + yc[2] * th;
0346 
0347                 } else if( _hs < 4.0 ) {
0348 
0349                     th = 4.0 - _hs;
0350                     tm = yc[2] + yc[1] * th;
0351 
0352                 } else if( _hs < 5.0 ) {
0353 
0354                     th = _hs - 4.0;
0355                     tm = yc[2] + yc[0] * th;
0356 
0357                 } else {
0358 
0359                     th = 6.0 - _hs;
0360                     tm = yc[0] + yc[2] * th;
0361 
0362                 }
0363 
0364                 // calculate RGB channels in sorted order
0365                 double tn, to, tp;
0366                 if( tm >= _y )
0367                 {
0368 
0369                     tp = _y + _y * _c * ( 1.0 - tm ) / tm;
0370                     to = _y + _y * _c * ( th - tm ) / tm;
0371                     tn = _y - ( _y * _c );
0372 
0373                 } else {
0374 
0375                     tp = _y + ( 1.0 - _y ) * _c;
0376                     to = _y + ( 1.0 - _y ) * _c * ( th - tm ) / ( 1.0 - tm );
0377                     tn = _y - ( 1.0 - _y ) * _c * tm / ( 1.0 - tm );
0378 
0379                 }
0380 
0381                 // return RGB channels in appropriate order
0382                 if( _hs < 1.0 ) return Rgba( igamma( tp ), igamma( to ), igamma( tn ), a );
0383                 else if( _hs < 2.0 ) return Rgba( igamma( to ), igamma( tp ), igamma( tn ), a );
0384                 else if( _hs < 3.0 ) return Rgba( igamma( tn ), igamma( tp ), igamma( to ), a );
0385                 else if( _hs < 4.0 ) return Rgba( igamma( tn ), igamma( to ), igamma( tp ), a );
0386                 else if( _hs < 5.0 ) return Rgba( igamma( to ), igamma( tn ), igamma( tp ), a );
0387                 else return Rgba( igamma( tp ), igamma( tn ), igamma( to ), a );
0388             }
0389 
0390             double h;
0391             double c;
0392             double y;
0393             double a;
0394 
0395         };
0396 
0397         //___________________________________________________________________
0398         static inline Rgba tintHelper( const Rgba &base, const Rgba &color, double amount )
0399         {
0400             HCY result( mix( base, color, pow( amount, 0.3 ) ) );
0401             result.y = mixdouble( luma( base ), result.y, amount );
0402 
0403             return result.rgba();
0404         }
0405 
0406     }
0407 
0408     //___________________________________________________________________
0409     double ColorUtils::luma( const ColorUtils::Rgba &color )
0410     {
0411 
0412         // RGB ratios
0413         return
0414             gamma( color.red() )*yc[0] +
0415             gamma( color.green() )*yc[1] +
0416             gamma( color.blue() )*yc[2];
0417     }
0418 
0419     //___________________________________________________________________
0420     double ColorUtils::contrastRatio( const ColorUtils::Rgba &c1, const ColorUtils::Rgba &c2 )
0421     {
0422         double y1 = luma( c1 ), y2 = luma( c2 );
0423         if( y1 > y2 ) return ( y1 + 0.05 ) / ( y2 + 0.05 );
0424         else return ( y2 + 0.05 ) / ( y1 + 0.05 );
0425     }
0426 
0427     //___________________________________________________________________
0428     ColorUtils::Rgba ColorUtils::lighten( const ColorUtils::Rgba &color, double ky, double kc )
0429     {
0430         HCY c( color );
0431         c.y = 1.0 - normalize( ( 1.0 - c.y ) * ( 1.0 - ky ) );
0432         c.c = 1.0 - normalize( ( 1.0 - c.c ) * kc );
0433         return c.rgba();
0434     }
0435 
0436     //___________________________________________________________________
0437     ColorUtils::Rgba ColorUtils::darken( const ColorUtils::Rgba &color, double ky, double kc )
0438     {
0439         HCY c( color );
0440         c.y = normalize( c.y * ( 1.0 - ky ) );
0441         c.c = normalize( c.c * kc );
0442         return c.rgba();
0443     }
0444 
0445     //___________________________________________________________________
0446     ColorUtils::Rgba ColorUtils::tint( const ColorUtils::Rgba &base, const ColorUtils::Rgba &color, double amount )
0447     {
0448         if( amount <= 0.0 ) return base;
0449         if( amount >= 1.0 ) return color;
0450         if( isnan( amount ) ) return base;
0451 
0452         double ri = contrastRatio( base, color );
0453         double rg = 1.0 + ( ( ri + 1.0 ) * amount * amount * amount );
0454         double u = 1.0;
0455         double l = 0.0;
0456         Rgba result;
0457         for ( int i = 12 ; i ; --i )
0458         {
0459 
0460             double a = 0.5 * ( l+u );
0461             result = tintHelper( base, color, a );
0462             double ra = contrastRatio( base, result );
0463             if( ra > rg ) u = a;
0464             else l = a;
0465 
0466         }
0467 
0468         return result;
0469 
0470     }
0471 
0472     //___________________________________________________________________
0473     ColorUtils::Rgba ColorUtils::mix( const ColorUtils::Rgba &c1, const ColorUtils::Rgba &c2, double bias )
0474     {
0475         if( bias <= 0.0 ) return c1;
0476         if( bias >= 1.0 ) return c2;
0477         if( isnan( bias ) ) return c1;
0478 
0479         double r = mixdouble( c1.red(),   c2.red(),   bias );
0480         double g = mixdouble( c1.green(), c2.green(), bias );
0481         double b = mixdouble( c1.blue(),  c2.blue(),  bias );
0482         double a = mixdouble( c1.alpha(), c2.alpha(), bias );
0483 
0484         return Rgba( r, g, b, a );
0485     }
0486 
0487     //___________________________________________________________________
0488     ColorUtils::Rgba ColorUtils::shade(const ColorUtils::Rgba &color, ColorUtils::ShadeRole role)
0489     { return shade(color, role, _contrast ); }
0490 
0491     //___________________________________________________________________
0492     ColorUtils::Rgba ColorUtils::shade(const ColorUtils::Rgba &color, ColorUtils::ShadeRole role, double contrast, double chromaAdjust)
0493     {
0494 
0495         // nan -> 1.0
0496         contrast = (1.0 > contrast ? (-1.0 < contrast ? contrast : -1.0) : 1.0);
0497         double y = luma(color), yi = 1.0 - y;
0498 
0499         if (y < 0.006)
0500         {
0501 
0502             // handle very dark colors (base, mid, dark, shadow == midlight, light)
0503             switch (role)
0504             {
0505 
0506                 case LightShade: return shade(color, 0.05 + 0.95 * contrast, chromaAdjust);
0507                 case MidShade: return shade(color, 0.01 + 0.20 * contrast, chromaAdjust);
0508                 case DarkShade: return shade(color, 0.02 + 0.40 * contrast, chromaAdjust);
0509                 default: return shade(color, 0.03 + 0.60 * contrast, chromaAdjust);
0510 
0511             }
0512 
0513         } else if (y > 0.93) {
0514 
0515             // handle very light colors (base, midlight, light == mid, dark, shadow)
0516             switch (role)
0517             {
0518 
0519                 case MidlightShade: return shade(color, -0.02 - 0.20 * contrast, chromaAdjust);
0520                 case DarkShade: return shade(color, -0.06 - 0.60 * contrast, chromaAdjust);
0521                 case ShadowShade: return shade(color, -0.10 - 0.90 * contrast, chromaAdjust);
0522                 default: return shade(color, -0.04 - 0.40 * contrast, chromaAdjust);
0523 
0524             }
0525 
0526         } else {
0527 
0528             // handle everything else
0529             double lightAmount = (0.05 + y * 0.55) * (0.25 + contrast * 0.75);
0530             double darkAmount =  (     - y       ) * (0.55 + contrast * 0.35);
0531             switch (role)
0532             {
0533                 case LightShade: return shade(color, lightAmount, chromaAdjust);
0534                 case MidlightShade: return shade(color, (0.15 + 0.35 * yi) * lightAmount, chromaAdjust);
0535                 case MidShade: return shade(color, (0.35 + 0.15 * y) * darkAmount, chromaAdjust);
0536                 case DarkShade: return shade(color, darkAmount, chromaAdjust);
0537                 default: return darken(shade(color, darkAmount, chromaAdjust), 0.5 + 0.3 * y);
0538             }
0539 
0540         }
0541 
0542     }
0543 
0544     //___________________________________________________________________
0545     ColorUtils::Rgba ColorUtils::shade( const ColorUtils::Rgba &color, double ky, double kc )
0546     {
0547         HCY c( color );
0548         c.y = normalize( c.y + ky );
0549         c.c = normalize( c.c + kc );
0550         return c.rgba();
0551     }
0552 
0553 
0554 }