Warning, file /plasma/oxygen-gtk/src/oxygencolorutils.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 }