browser/metro/modules/colorUtils.jsm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5 'use strict';
michael@0 6 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 7 Components.utils.import("resource://gre/modules/Promise.jsm");
michael@0 8
michael@0 9 const ColorAnalyzer = Components.classes["@mozilla.org/places/colorAnalyzer;1"]
michael@0 10 .getService(Components.interfaces.mozIColorAnalyzer);
michael@0 11
michael@0 12 this.EXPORTED_SYMBOLS = ["ColorUtils"];
michael@0 13
michael@0 14 let ColorUtils = {
michael@0 15 _initialized: false,
michael@0 16 init: function() {
michael@0 17 if (this._initialized)
michael@0 18 return;
michael@0 19 Services.obs.addObserver(this, "idle-daily", false);
michael@0 20 Services.obs.addObserver(this, "quit-application", false);
michael@0 21 this._initialized = true;
michael@0 22 },
michael@0 23 uninit: function() {
michael@0 24 Services.obs.removeObserver(this, "idle-daily");
michael@0 25 Services.obs.removeObserver(this, "quit-application");
michael@0 26 },
michael@0 27
michael@0 28 // default to keeping icon colorInfo for max 1 day
michael@0 29 iconColorCacheMaxAge: 86400000,
michael@0 30
michael@0 31 // in-memory store for favicon color data
michael@0 32 _uriColorsMap: (function() {
michael@0 33 let cache = new Map();
michael@0 34 // remove stale entries
michael@0 35 cache.purge = function(maxAgeMs = 0) {
michael@0 36 let cuttoff = Date.now() - (maxAgeMs || ColorUtils.iconColorCacheMaxAge);
michael@0 37 for (let [key, value] of this) {
michael@0 38 if (value.timestamp && value.timestamp >= cuttoff) {
michael@0 39 continue;
michael@0 40 }
michael@0 41 this.delete(key);
michael@0 42 }
michael@0 43 }
michael@0 44 return cache;
michael@0 45 })(),
michael@0 46 get iconColorCache() {
michael@0 47 return ColorUtils._uriColorsMap;
michael@0 48 },
michael@0 49
michael@0 50 observe: function (aSubject, aTopic, aData) {
michael@0 51 switch (aTopic) {
michael@0 52 case "idle-daily":
michael@0 53 this.iconColorCache.purge();
michael@0 54 break;
michael@0 55 case "quit-application":
michael@0 56 this.uninit();
michael@0 57 break;
michael@0 58 }
michael@0 59 },
michael@0 60
michael@0 61 /** Takes an icon and returns either an object with foreground and background color properties
michael@0 62 * or a promise for the same.
michael@0 63 * The foreground is the contrast color, the background is the primary/dominant one
michael@0 64 */
michael@0 65 getForegroundAndBackgroundIconColors: function getForegroundAndBackgroundIconColors(aIconURI) {
michael@0 66 let colorKey = aIconURI.spec;
michael@0 67 let colorInfo = this._uriColorsMap.get(colorKey);
michael@0 68 if (colorInfo) {
michael@0 69 return colorInfo;
michael@0 70 }
michael@0 71
michael@0 72 let deferred = Promise.defer();
michael@0 73 let wrappedIcon = aIconURI;
michael@0 74 this._uriColorsMap.set(colorKey, deferred.promise);
michael@0 75
michael@0 76 ColorAnalyzer.findRepresentativeColor(wrappedIcon, (success, color) => {
michael@0 77 if (!success) {
michael@0 78 this._uriColorsMap.delete(colorKey);
michael@0 79 deferred.reject();
michael@0 80 } else {
michael@0 81 colorInfo = {
michael@0 82 foreground: this.bestTextColorForContrast(color),
michael@0 83 background: this.convertDecimalToRgbColor(color),
michael@0 84 timestamp: Date.now()
michael@0 85 };
michael@0 86 deferred.resolve(colorInfo);
michael@0 87 }
michael@0 88 });
michael@0 89 return deferred.promise;
michael@0 90 },
michael@0 91
michael@0 92 /** returns the best color for text readability on top of aColor
michael@0 93 * return color is in rgb(r,g,b) format, suitable to csss
michael@0 94 * The color bightness algorithm is currently: http://www.w3.org/TR/AERT#color-contrast
michael@0 95 */
michael@0 96 bestTextColorForContrast: function bestTextColorForContrast(aColor) {
michael@0 97 let r = (aColor & 0xff0000) >> 16;
michael@0 98 let g = (aColor & 0x00ff00) >> 8;
michael@0 99 let b = (aColor & 0x0000ff);
michael@0 100
michael@0 101 let w3cContrastValue = ((r*299)+(g*587)+(b*114))/1000;
michael@0 102 w3cContrastValue = Math.round(w3cContrastValue);
michael@0 103 let textColor = "rgb(255,255,255)";
michael@0 104
michael@0 105 if (w3cContrastValue > 125) {
michael@0 106 // bright/light, use black text
michael@0 107 textColor = "rgb(0,0,0)";
michael@0 108 }
michael@0 109 return textColor;
michael@0 110 },
michael@0 111
michael@0 112 toCSSRgbColor: function toCSSRgbColor(r, g, b, a) {
michael@0 113 var values = [Math.round(r), Math.round(g), Math.round(b)];
michael@0 114 if (undefined !== a && a < 1) {
michael@0 115 values.push(a);
michael@0 116 return 'rgba('+values.join(',')+')';
michael@0 117 }
michael@0 118 return 'rgb('+values.join(',')+')';
michael@0 119 },
michael@0 120
michael@0 121 /**
michael@0 122 * converts a decimal(base10) number into CSS rgb color value string
michael@0 123 */
michael@0 124 convertDecimalToRgbColor: function convertDecimalToRgbColor(aColor) {
michael@0 125 let [r,g,b,a] = this.unpackDecimalColorWord(aColor);
michael@0 126 return this.toCSSRgbColor(r,g,b,a);
michael@0 127 },
michael@0 128
michael@0 129 /**
michael@0 130 * unpack a decimal(base10) word for r,g,b,a values
michael@0 131 */
michael@0 132 unpackDecimalColorWord: function unpackDecimalColorWord(aColor) {
michael@0 133 let a = (aColor & 0xff000000) >> 24;
michael@0 134 let r = (aColor & 0x00ff0000) >> 16;
michael@0 135 let g = (aColor & 0x0000ff00) >> 8;
michael@0 136 let b = (aColor & 0x000000ff);
michael@0 137 // NB: falsy alpha treated as undefined, fully opaque
michael@0 138 return a ? [r,g,b,a/255] : [r,g,b];
michael@0 139 },
michael@0 140
michael@0 141 /**
michael@0 142 * create a decimal(base10) word for r,g,b values
michael@0 143 */
michael@0 144 createDecimalColorWord: function createDecimalColorWord(r, g, b, a) {
michael@0 145 let rgb = 0;
michael@0 146 rgb |= b;
michael@0 147 rgb |= (g << 8);
michael@0 148 rgb |= (r << 16);
michael@0 149 // pack alpha value if one is given
michael@0 150 if (undefined !== a && a < 1)
michael@0 151 rgb |= (Math.round(a*255) << 24);
michael@0 152 return rgb;
michael@0 153 },
michael@0 154
michael@0 155 /**
michael@0 156 * Add 2 rgb(a) colors to get a flat color
michael@0 157 */
michael@0 158 addRgbColors: function addRgbColors(color1, color2) {
michael@0 159 let [r1, g1, b1] = this.unpackDecimalColorWord(color1);
michael@0 160 let [r2, g2, b2, alpha] = this.unpackDecimalColorWord(color2);
michael@0 161
michael@0 162 let color = {};
michael@0 163 // early return if 2nd color is opaque
michael@0 164 if (!alpha || alpha >= 1)
michael@0 165 return color2;
michael@0 166
michael@0 167 return this.createDecimalColorWord(
michael@0 168 Math.min(255, alpha * r2 + (1 - alpha) * r1),
michael@0 169 Math.min(255, alpha * g2 + (1 - alpha) * g1),
michael@0 170 Math.min(255, alpha * b2 + (1 - alpha) * b1)
michael@0 171 );
michael@0 172 }
michael@0 173 };

mercurial