1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/modules/colorUtils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,173 @@ 1.4 +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 +'use strict'; 1.9 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.10 +Components.utils.import("resource://gre/modules/Promise.jsm"); 1.11 + 1.12 +const ColorAnalyzer = Components.classes["@mozilla.org/places/colorAnalyzer;1"] 1.13 + .getService(Components.interfaces.mozIColorAnalyzer); 1.14 + 1.15 +this.EXPORTED_SYMBOLS = ["ColorUtils"]; 1.16 + 1.17 +let ColorUtils = { 1.18 + _initialized: false, 1.19 + init: function() { 1.20 + if (this._initialized) 1.21 + return; 1.22 + Services.obs.addObserver(this, "idle-daily", false); 1.23 + Services.obs.addObserver(this, "quit-application", false); 1.24 + this._initialized = true; 1.25 + }, 1.26 + uninit: function() { 1.27 + Services.obs.removeObserver(this, "idle-daily"); 1.28 + Services.obs.removeObserver(this, "quit-application"); 1.29 + }, 1.30 + 1.31 + // default to keeping icon colorInfo for max 1 day 1.32 + iconColorCacheMaxAge: 86400000, 1.33 + 1.34 + // in-memory store for favicon color data 1.35 + _uriColorsMap: (function() { 1.36 + let cache = new Map(); 1.37 + // remove stale entries 1.38 + cache.purge = function(maxAgeMs = 0) { 1.39 + let cuttoff = Date.now() - (maxAgeMs || ColorUtils.iconColorCacheMaxAge); 1.40 + for (let [key, value] of this) { 1.41 + if (value.timestamp && value.timestamp >= cuttoff) { 1.42 + continue; 1.43 + } 1.44 + this.delete(key); 1.45 + } 1.46 + } 1.47 + return cache; 1.48 + })(), 1.49 + get iconColorCache() { 1.50 + return ColorUtils._uriColorsMap; 1.51 + }, 1.52 + 1.53 + observe: function (aSubject, aTopic, aData) { 1.54 + switch (aTopic) { 1.55 + case "idle-daily": 1.56 + this.iconColorCache.purge(); 1.57 + break; 1.58 + case "quit-application": 1.59 + this.uninit(); 1.60 + break; 1.61 + } 1.62 + }, 1.63 + 1.64 + /** Takes an icon and returns either an object with foreground and background color properties 1.65 + * or a promise for the same. 1.66 + * The foreground is the contrast color, the background is the primary/dominant one 1.67 + */ 1.68 + getForegroundAndBackgroundIconColors: function getForegroundAndBackgroundIconColors(aIconURI) { 1.69 + let colorKey = aIconURI.spec; 1.70 + let colorInfo = this._uriColorsMap.get(colorKey); 1.71 + if (colorInfo) { 1.72 + return colorInfo; 1.73 + } 1.74 + 1.75 + let deferred = Promise.defer(); 1.76 + let wrappedIcon = aIconURI; 1.77 + this._uriColorsMap.set(colorKey, deferred.promise); 1.78 + 1.79 + ColorAnalyzer.findRepresentativeColor(wrappedIcon, (success, color) => { 1.80 + if (!success) { 1.81 + this._uriColorsMap.delete(colorKey); 1.82 + deferred.reject(); 1.83 + } else { 1.84 + colorInfo = { 1.85 + foreground: this.bestTextColorForContrast(color), 1.86 + background: this.convertDecimalToRgbColor(color), 1.87 + timestamp: Date.now() 1.88 + }; 1.89 + deferred.resolve(colorInfo); 1.90 + } 1.91 + }); 1.92 + return deferred.promise; 1.93 + }, 1.94 + 1.95 + /** returns the best color for text readability on top of aColor 1.96 + * return color is in rgb(r,g,b) format, suitable to csss 1.97 + * The color bightness algorithm is currently: http://www.w3.org/TR/AERT#color-contrast 1.98 + */ 1.99 + bestTextColorForContrast: function bestTextColorForContrast(aColor) { 1.100 + let r = (aColor & 0xff0000) >> 16; 1.101 + let g = (aColor & 0x00ff00) >> 8; 1.102 + let b = (aColor & 0x0000ff); 1.103 + 1.104 + let w3cContrastValue = ((r*299)+(g*587)+(b*114))/1000; 1.105 + w3cContrastValue = Math.round(w3cContrastValue); 1.106 + let textColor = "rgb(255,255,255)"; 1.107 + 1.108 + if (w3cContrastValue > 125) { 1.109 + // bright/light, use black text 1.110 + textColor = "rgb(0,0,0)"; 1.111 + } 1.112 + return textColor; 1.113 + }, 1.114 + 1.115 + toCSSRgbColor: function toCSSRgbColor(r, g, b, a) { 1.116 + var values = [Math.round(r), Math.round(g), Math.round(b)]; 1.117 + if (undefined !== a && a < 1) { 1.118 + values.push(a); 1.119 + return 'rgba('+values.join(',')+')'; 1.120 + } 1.121 + return 'rgb('+values.join(',')+')'; 1.122 + }, 1.123 + 1.124 + /** 1.125 + * converts a decimal(base10) number into CSS rgb color value string 1.126 + */ 1.127 + convertDecimalToRgbColor: function convertDecimalToRgbColor(aColor) { 1.128 + let [r,g,b,a] = this.unpackDecimalColorWord(aColor); 1.129 + return this.toCSSRgbColor(r,g,b,a); 1.130 + }, 1.131 + 1.132 + /** 1.133 + * unpack a decimal(base10) word for r,g,b,a values 1.134 + */ 1.135 + unpackDecimalColorWord: function unpackDecimalColorWord(aColor) { 1.136 + let a = (aColor & 0xff000000) >> 24; 1.137 + let r = (aColor & 0x00ff0000) >> 16; 1.138 + let g = (aColor & 0x0000ff00) >> 8; 1.139 + let b = (aColor & 0x000000ff); 1.140 + // NB: falsy alpha treated as undefined, fully opaque 1.141 + return a ? [r,g,b,a/255] : [r,g,b]; 1.142 + }, 1.143 + 1.144 + /** 1.145 + * create a decimal(base10) word for r,g,b values 1.146 + */ 1.147 + createDecimalColorWord: function createDecimalColorWord(r, g, b, a) { 1.148 + let rgb = 0; 1.149 + rgb |= b; 1.150 + rgb |= (g << 8); 1.151 + rgb |= (r << 16); 1.152 + // pack alpha value if one is given 1.153 + if (undefined !== a && a < 1) 1.154 + rgb |= (Math.round(a*255) << 24); 1.155 + return rgb; 1.156 + }, 1.157 + 1.158 + /** 1.159 + * Add 2 rgb(a) colors to get a flat color 1.160 + */ 1.161 + addRgbColors: function addRgbColors(color1, color2) { 1.162 + let [r1, g1, b1] = this.unpackDecimalColorWord(color1); 1.163 + let [r2, g2, b2, alpha] = this.unpackDecimalColorWord(color2); 1.164 + 1.165 + let color = {}; 1.166 + // early return if 2nd color is opaque 1.167 + if (!alpha || alpha >= 1) 1.168 + return color2; 1.169 + 1.170 + return this.createDecimalColorWord( 1.171 + Math.min(255, alpha * r2 + (1 - alpha) * r1), 1.172 + Math.min(255, alpha * g2 + (1 - alpha) * g1), 1.173 + Math.min(255, alpha * b2 + (1 - alpha) * b1) 1.174 + ); 1.175 + } 1.176 +};