browser/metro/modules/colorUtils.jsm

changeset 0
6474c204b198
     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 +};

mercurial